diff options
| author | Andrew Godwin <andrew@aeracode.org> | 2013-04-18 17:16:39 +0100 |
|---|---|---|
| committer | Andrew Godwin <andrew@aeracode.org> | 2013-04-18 17:16:39 +0100 |
| commit | 7f3678dc4cd7146c49bac3fb8f5211f647636aa3 (patch) | |
| tree | fdd2a60ccd1a9a3162d89f970db44502e35a3b3f /tests/utils_tests | |
| parent | b62e82365ad56ca930f7abb1d1dbdf9ce5a7c7c3 (diff) | |
| parent | 93c1576f17f6c5ee73f94f8de007f3c33010dc81 (diff) | |
Merge branch 'master' into schema-alteration
Conflicts:
django/db/backends/__init__.py
django/db/backends/mysql/base.py
django/db/backends/oracle/base.py
django/db/backends/oracle/creation.py
django/db/backends/postgresql_psycopg2/base.py
django/db/backends/sqlite3/base.py
django/db/models/fields/related.py
Diffstat (limited to 'tests/utils_tests')
41 files changed, 4232 insertions, 0 deletions
diff --git a/tests/utils_tests/__init__.py b/tests/utils_tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/utils_tests/__init__.py diff --git a/tests/utils_tests/archives/foobar.tar b/tests/utils_tests/archives/foobar.tar Binary files differnew file mode 100644 index 0000000000..5418405070 --- /dev/null +++ b/tests/utils_tests/archives/foobar.tar diff --git a/tests/utils_tests/archives/foobar.tar.bz2 b/tests/utils_tests/archives/foobar.tar.bz2 Binary files differnew file mode 100644 index 0000000000..693529dcef --- /dev/null +++ b/tests/utils_tests/archives/foobar.tar.bz2 diff --git a/tests/utils_tests/archives/foobar.tar.gz b/tests/utils_tests/archives/foobar.tar.gz Binary files differnew file mode 100644 index 0000000000..5b596bd58e --- /dev/null +++ b/tests/utils_tests/archives/foobar.tar.gz diff --git a/tests/utils_tests/archives/foobar.zip b/tests/utils_tests/archives/foobar.zip Binary files differnew file mode 100644 index 0000000000..dc3e2fc067 --- /dev/null +++ b/tests/utils_tests/archives/foobar.zip diff --git a/tests/utils_tests/eggs/test_egg.egg b/tests/utils_tests/eggs/test_egg.egg Binary files differnew file mode 100644 index 0000000000..9b08cc10ef --- /dev/null +++ b/tests/utils_tests/eggs/test_egg.egg diff --git a/tests/utils_tests/files/strip_tags1.html b/tests/utils_tests/files/strip_tags1.html new file mode 100644 index 0000000000..8e9ee1d8fd --- /dev/null +++ b/tests/utils_tests/files/strip_tags1.html @@ -0,0 +1,1302 @@ +<!DOCTYPE html> +<html> + <head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# githubog: http://ogp.me/ns/fb/githubog#"> + <meta charset='utf-8'> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <title>Improved regex in strip_tags · d7504a3 · django/django</title> + <link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="GitHub" /> + <link rel="fluid-icon" href="https://github.com/fluidicon.png" title="GitHub" /> + <link rel="apple-touch-icon" sizes="57x57" href="/apple-touch-icon-114.png" /> + <link rel="apple-touch-icon" sizes="114x114" href="/apple-touch-icon-114.png" /> + <link rel="apple-touch-icon" sizes="72x72" href="/apple-touch-icon-144.png" /> + <link rel="apple-touch-icon" sizes="144x144" href="/apple-touch-icon-144.png" /> + <link rel="logo" type="image/svg" href="http://github-media-downloads.s3.amazonaws.com/github-logo.svg" /> + <meta name="msapplication-TileImage" content="/windows-tile.png"> + <meta name="msapplication-TileColor" content="#ffffff"> + + <link rel="icon" type="image/x-icon" href="/favicon.ico" /> + + <meta content="authenticity_token" name="csrf-param" /> +<meta content="Vbmuc30dLLFdm7POIe3xfTa4nODYc/la/wLrI1OLEOI=" name="csrf-token" /> + + <link href="https://a248.e.akamai.net/assets.github.com/assets/github-f70e4783e00fd4884a9e5e651a43933c9881caa8.css" media="all" rel="stylesheet" type="text/css" /> + <link href="https://a248.e.akamai.net/assets.github.com/assets/github2-0d31290d073dea4d8671e2b8c747629aeb074034.css" media="all" rel="stylesheet" type="text/css" /> + + + + <script src="https://a248.e.akamai.net/assets.github.com/assets/frameworks-d76b58e749b52bc47a4c46620bf2c320fabe5248.js" type="text/javascript"></script> + <script src="https://a248.e.akamai.net/assets.github.com/assets/github-67b55380cff8d6766b298e6935a3c1db7d5c6d5d.js" type="text/javascript"></script> + + <meta http-equiv="x-pjax-version" content="1212ad79754350a805cefbcd08a3dadf"> + + <link data-pjax-transient rel='alternate' type='text/plain+diff' href="/django/django/commit/d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e.diff"> + <link data-pjax-transient rel='alternate' type='text/plain+patch' href="/django/django/commit/d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e.patch"> + <link data-pjax-transient rel='permalink' type='text/html' href="/django/django/commit/d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e"> + <meta property="og:title" content="django"/> + <meta property="og:type" content="githubog:gitrepository"/> + <meta property="og:url" content="https://github.com/django/django"/> + <meta property="og:image" content="https://secure.gravatar.com/avatar/fd542381031aa84dca86628ece84fc07?s=420&d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png"/> + <meta property="og:site_name" content="GitHub"/> + <meta property="og:description" content="django - The Web framework for perfectionists with deadlines."/> + <meta property="twitter:card" content="summary"/> + <meta property="twitter:site" content="@GitHub"> + <meta property="twitter:title" content="django/django"/> + + <meta name="description" content="django - The Web framework for perfectionists with deadlines." /> + + <link href="https://github.com/django/django/commits/d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e.atom" rel="alternate" title="Recent Commits to django:d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e" type="application/atom+xml" /> + + </head> + + + <body class="logged_in linux vis-public env-production "> + <div id="wrapper"> + + <div class="header header-logged-in true"> + <div class="container clearfix"> + + <a class="header-logo-blacktocat" href="https://github.com/"> + <span class="mega-icon mega-icon-blacktocat"></span> +</a> + + <div class="divider-vertical"></div> + + <a href="/django/django/notifications" class="notification-indicator tooltipped downwards contextually-unread" title="You have unread notifications in this repository"> + <span class="mail-status unread"></span> + </a> + <div class="divider-vertical"></div> + + + <div class="command-bar js-command-bar "> + <form accept-charset="UTF-8" action="/search" class="command-bar-form" id="top_search_form" method="get"> + <a href="/search/advanced" class="advanced-search-icon tooltipped downwards command-bar-search" id="advanced_search" title="Advanced search"><span class="mini-icon mini-icon-advanced-search "></span></a> + + <input type="text" name="q" id="js-command-bar-field" placeholder="Search or type a command" tabindex="1" data-username="claudep" autocapitalize="off"> + + <span class="mini-icon help tooltipped downwards" title="Show command bar help"> + <span class="mini-icon mini-icon-help"></span> + </span> + + <input type="hidden" name="ref" value="commandbar"> + + <div class="divider-vertical"></div> +</form> + <ul class="top-nav"> + <li class="explore"><a href="https://github.com/explore">Explore</a></li> + <li><a href="https://gist.github.com">Gist</a></li> + <li><a href="/blog">Blog</a></li> + <li><a href="http://help.github.com">Help</a></li> + </ul> + </div> + + + + + <ul id="user-links"> + <li> + <a href="https://github.com/claudep" class="name"> + <img height="20" src="https://secure.gravatar.com/avatar/cf4198670f0073174b475634964b576b?s=140&d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png" width="20" /> claudep + </a> + </li> + <li> + <a href="/new" id="new_repo" class="tooltipped downwards" title="Create a new repo"> + <span class="mini-icon mini-icon-create"></span> + </a> + </li> + <li> + <a href="/settings/profile" id="account_settings" + class="tooltipped downwards" + title="Account settings "> + <span class="mini-icon mini-icon-account-settings"></span> + </a> + </li> + <li> + <a href="/logout" data-method="post" id="logout" class="tooltipped downwards" title="Sign out"> + <span class="mini-icon mini-icon-logout"></span> + </a> + </li> + </ul> + + + + + </div> +</div> + + <div class="site hfeed" itemscope itemtype="http://schema.org/WebPage"> + <div class="hentry"> + + <div class="pagehead repohead instapaper_ignore readability-menu "> + <div class="container"> + <div class="title-actions-bar"> + + + +<ul class="pagehead-actions"> + + <li class="nspr"> + <a href="/django/django/pull/new/d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e" class="button minibutton btn-pull-request" icon_class="mini-icon-pull-request"><span class="mini-icon mini-icon-pull-request"></span>Pull Request</a> + </li> + + <li class="subscription"> + <form accept-charset="UTF-8" action="/notifications/subscribe" data-autosubmit="true" data-remote="true" method="post"><div style="margin:0;padding:0;display:inline"><input name="authenticity_token" type="hidden" value="Vbmuc30dLLFdm7POIe3xfTa4nODYc/la/wLrI1OLEOI=" /></div> <input id="repository_id" name="repository_id" type="hidden" value="4164482" /> + + <div class="select-menu js-menu-container js-select-menu"> + <span class="minibutton select-menu-button js-menu-target"> + <span class="js-select-button"> + <span class="mini-icon mini-icon-watching"></span> + Watch + </span> + </span> + + <div class="select-menu-modal-holder js-menu-content"> + <div class="select-menu-modal"> + <div class="select-menu-header"> + <span class="select-menu-title">Notification status</span> + <span class="mini-icon mini-icon-remove-close js-menu-close"></span> + </div> <!-- /.select-menu-header --> + + <div class="select-menu-list js-navigation-container"> + + <div class="select-menu-item js-navigation-item js-navigation-target selected"> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <div class="select-menu-item-text"> + <input checked="checked" id="do_included" name="do" type="radio" value="included" /> + <h4>Not watching</h4> + <span class="description">You only receive notifications for discussions in which you participate or are @mentioned.</span> + <span class="js-select-button-text hidden-select-button-text"> + <span class="mini-icon mini-icon-watching"></span> + Watch + </span> + </div> + </div> <!-- /.select-menu-item --> + + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <div class="select-menu-item-text"> + <input id="do_subscribed" name="do" type="radio" value="subscribed" /> + <h4>Watching</h4> + <span class="description">You receive notifications for all discussions in this repository.</span> + <span class="js-select-button-text hidden-select-button-text"> + <span class="mini-icon mini-icon-unwatch"></span> + Unwatch + </span> + </div> + </div> <!-- /.select-menu-item --> + + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <div class="select-menu-item-text"> + <input id="do_ignore" name="do" type="radio" value="ignore" /> + <h4>Ignoring</h4> + <span class="description">You do not receive any notifications for discussions in this repository.</span> + <span class="js-select-button-text hidden-select-button-text"> + <span class="mini-icon mini-icon-mute"></span> + Stop ignoring + </span> + </div> + </div> <!-- /.select-menu-item --> + + </div> <!-- /.select-menu-list --> + + </div> <!-- /.select-menu-modal --> + </div> <!-- /.select-menu-modal-holder --> + </div> <!-- /.select-menu --> + +</form> + </li> + + <li class="js-toggler-container js-social-container starring-container on"> + <a href="/django/django/unstar" class="minibutton js-toggler-target star-button starred upwards" title="Unstar this repo" data-remote="true" data-method="post" rel="nofollow"> + <span class="mini-icon mini-icon-remove-star"></span> + <span class="text">Unstar</span> + </a> + <a href="/django/django/star" class="minibutton js-toggler-target star-button unstarred upwards" title="Star this repo" data-remote="true" data-method="post" rel="nofollow"> + <span class="mini-icon mini-icon-star"></span> + <span class="text">Star</span> + </a> + <a class="social-count js-social-count" href="/django/django/stargazers">5,939</a> + </li> + + <li> + <a href="/django/django/fork_select" class="minibutton js-toggler-target fork-button lighter upwards" title="Fork this repo" rel="facebox nofollow"> + <span class="mini-icon mini-icon-branch-create"></span> + <span class="text">Fork</span> + </a> + <a href="/django/django/network" class="social-count">1,909</a> + </li> + + +</ul> + + <h1 itemscope itemtype="http://data-vocabulary.org/Breadcrumb" class="entry-title public"> + <span class="repo-label"><span>public</span></span> + <span class="mega-icon mega-icon-public-repo"></span> + <span class="author vcard"> + <a href="/django" class="url fn" itemprop="url" rel="author"> + <span itemprop="title">django</span> + </a></span> / + <strong><a href="/django/django" class="js-current-repository">django</a></strong> + </h1> + </div> + + + <ul class="tabs"> + <li><a href="/django/django" class="selected" highlight="repo_source repo_downloads repo_commits repo_tags repo_branches">Code</a></li> + <li><a href="/django/django/network" highlight="repo_network">Network</a></li> + <li><a href="/django/django/pulls" highlight="repo_pulls">Pull Requests <span class='counter'>177</span></a></li> + + + + + <li><a href="/django/django/graphs" highlight="repo_graphs repo_contributors">Graphs</a></li> + + <li> + <a href="/django/django/settings">Settings</a> + </li> + + </ul> + +<div class="tabnav"> + + <span class="tabnav-right"> + <ul class="tabnav-tabs"> + <li><a href="/django/django/tags" class="tabnav-tab" highlight="repo_tags">Tags <span class="counter ">39</span></a></li> + </ul> + + </span> + + <div class="tabnav-widget scope"> + + + <div class="select-menu js-menu-container js-select-menu js-branch-menu"> + <a class="minibutton select-menu-button js-menu-target" data-hotkey="w" data-ref=""> + <span class="mini-icon mini-icon-tree"></span> + <i>tree:</i> + <span class="js-select-button">d7504a3d7b</span> + </a> + + <div class="select-menu-modal-holder js-menu-content js-navigation-container"> + + <div class="select-menu-modal"> + <div class="select-menu-header"> + <span class="select-menu-title">Switch branches/tags</span> + <span class="mini-icon mini-icon-remove-close js-menu-close"></span> + </div> <!-- /.select-menu-header --> + + <div class="select-menu-filters"> + <div class="select-menu-text-filter"> + <input type="text" id="commitish-filter-field" class="js-filterable-field js-navigation-enable" placeholder="Find or create a branch…"> + </div> + <div class="select-menu-tabs"> + <ul> + <li class="select-menu-tab"> + <a href="#" data-tab-filter="branches" class="js-select-menu-tab">Branches</a> + </li> + <li class="select-menu-tab"> + <a href="#" data-tab-filter="tags" class="js-select-menu-tab">Tags</a> + </li> + </ul> + </div><!-- /.select-menu-tabs --> + </div><!-- /.select-menu-filters --> + + <div class="select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket css-truncate" data-tab-filter="branches"> + + <div data-filterable-for="commitish-filter-field" data-filterable-type="substring"> + + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/boulder-oracle-sprint" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/boulder-oracle-sprint" rel="nofollow" title="attic/boulder-oracle-sprint">attic/boulder-oracle-sprint</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/full-history" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/full-history" rel="nofollow" title="attic/full-history">attic/full-history</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/generic-auth" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/generic-auth" rel="nofollow" title="attic/generic-auth">attic/generic-auth</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/gis" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/gis" rel="nofollow" title="attic/gis">attic/gis</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/i18n" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/i18n" rel="nofollow" title="attic/i18n">attic/i18n</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/magic-removal" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/magic-removal" rel="nofollow" title="attic/magic-removal">attic/magic-removal</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/multi-auth" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/multi-auth" rel="nofollow" title="attic/multi-auth">attic/multi-auth</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/multiple-db-support" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/multiple-db-support" rel="nofollow" title="attic/multiple-db-support">attic/multiple-db-support</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/new-admin" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/new-admin" rel="nofollow" title="attic/new-admin">attic/new-admin</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/newforms-admin" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/newforms-admin" rel="nofollow" title="attic/newforms-admin">attic/newforms-admin</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/per-object-permissions" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/per-object-permissions" rel="nofollow" title="attic/per-object-permissions">attic/per-object-permissions</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/queryset-refactor" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/queryset-refactor" rel="nofollow" title="attic/queryset-refactor">attic/queryset-refactor</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/schema-evolution" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/schema-evolution" rel="nofollow" title="attic/schema-evolution">attic/schema-evolution</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/schema-evolution-ng" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/schema-evolution-ng" rel="nofollow" title="attic/schema-evolution-ng">attic/schema-evolution-ng</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/search-api" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/search-api" rel="nofollow" title="attic/search-api">attic/search-api</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/sqlalchemy" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/sqlalchemy" rel="nofollow" title="attic/sqlalchemy">attic/sqlalchemy</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/unicode" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/unicode" rel="nofollow" title="attic/unicode">attic/unicode</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/master" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="master" rel="nofollow" title="master">master</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/soc2009/admin-ui" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="soc2009/admin-ui" rel="nofollow" title="soc2009/admin-ui">soc2009/admin-ui</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/soc2009/http-wsgi-improvements" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="soc2009/http-wsgi-improvements" rel="nofollow" title="soc2009/http-wsgi-improvements">soc2009/http-wsgi-improvements</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/soc2009/i18n-improvements" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="soc2009/i18n-improvements" rel="nofollow" title="soc2009/i18n-improvements">soc2009/i18n-improvements</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/soc2009/model-validation" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="soc2009/model-validation" rel="nofollow" title="soc2009/model-validation">soc2009/model-validation</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/soc2009/multidb" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="soc2009/multidb" rel="nofollow" title="soc2009/multidb">soc2009/multidb</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/soc2009/test-improvements" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="soc2009/test-improvements" rel="nofollow" title="soc2009/test-improvements">soc2009/test-improvements</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/soc2010/app-loading" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="soc2010/app-loading" rel="nofollow" title="soc2010/app-loading">soc2010/app-loading</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/soc2010/query-refactor" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="soc2010/query-refactor" rel="nofollow" title="soc2010/query-refactor">soc2010/query-refactor</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/soc2010/test-refactor" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="soc2010/test-refactor" rel="nofollow" title="soc2010/test-refactor">soc2010/test-refactor</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/stable/0.90.x" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="stable/0.90.x" rel="nofollow" title="stable/0.90.x">stable/0.90.x</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/stable/0.91.x" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="stable/0.91.x" rel="nofollow" title="stable/0.91.x">stable/0.91.x</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/stable/0.95.x" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="stable/0.95.x" rel="nofollow" title="stable/0.95.x">stable/0.95.x</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/stable/0.96.x" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="stable/0.96.x" rel="nofollow" title="stable/0.96.x">stable/0.96.x</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/stable/1.0.x" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="stable/1.0.x" rel="nofollow" title="stable/1.0.x">stable/1.0.x</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/stable/1.1.x" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="stable/1.1.x" rel="nofollow" title="stable/1.1.x">stable/1.1.x</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/stable/1.2.x" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="stable/1.2.x" rel="nofollow" title="stable/1.2.x">stable/1.2.x</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/stable/1.3.x" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="stable/1.3.x" rel="nofollow" title="stable/1.3.x">stable/1.3.x</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/stable/1.4.x" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="stable/1.4.x" rel="nofollow" title="stable/1.4.x">stable/1.4.x</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/stable/1.5.x" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="stable/1.5.x" rel="nofollow" title="stable/1.5.x">stable/1.5.x</a> + </div> <!-- /.select-menu-item --> + </div> + + <form accept-charset="UTF-8" action="/django/django/branches" class="js-create-branch select-menu-item select-menu-new-item-form js-navigation-item js-navigation-target js-new-item-form" method="post"><div style="margin:0;padding:0;display:inline"><input name="authenticity_token" type="hidden" value="Vbmuc30dLLFdm7POIe3xfTa4nODYc/la/wLrI1OLEOI=" /></div> + + <span class="mini-icon mini-icon-branch-create select-menu-item-icon"></span> + <div class="select-menu-item-text"> + <h4>Create branch: <span class="js-new-item-name"></span></h4> + <span class="description">from ‘d7504a3’</span> + </div> + <input type="hidden" name="name" id="name" class="js-new-item-submit" /> + <input type="hidden" name="branch" id="branch" value="d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e" /> + <input type="hidden" name="path" id="branch" value="" /> + </form> <!-- /.select-menu-item --> + + </div> <!-- /.select-menu-list --> + + + <div class="select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket css-truncate" data-tab-filter="tags"> + <div data-filterable-for="commitish-filter-field" data-filterable-type="substring"> + + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.5c2" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.5c2" rel="nofollow" title="1.5c2">1.5c2</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.5c1" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.5c1" rel="nofollow" title="1.5c1">1.5c1</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.5b2" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.5b2" rel="nofollow" title="1.5b2">1.5b2</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.5b1" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.5b1" rel="nofollow" title="1.5b1">1.5b1</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.5a1" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.5a1" rel="nofollow" title="1.5a1">1.5a1</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.5.1" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.5.1" rel="nofollow" title="1.5.1">1.5.1</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.5" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.5" rel="nofollow" title="1.5">1.5</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.4.5" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.4.5" rel="nofollow" title="1.4.5">1.4.5</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.4.4" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.4.4" rel="nofollow" title="1.4.4">1.4.4</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.4.3" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.4.3" rel="nofollow" title="1.4.3">1.4.3</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.4.2" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.4.2" rel="nofollow" title="1.4.2">1.4.2</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.4.1" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.4.1" rel="nofollow" title="1.4.1">1.4.1</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.4" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.4" rel="nofollow" title="1.4">1.4</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.3.7" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.3.7" rel="nofollow" title="1.3.7">1.3.7</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.3.6" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.3.6" rel="nofollow" title="1.3.6">1.3.6</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.3.5" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.3.5" rel="nofollow" title="1.3.5">1.3.5</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.3.4" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.3.4" rel="nofollow" title="1.3.4">1.3.4</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.3.3" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.3.3" rel="nofollow" title="1.3.3">1.3.3</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.3.2" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.3.2" rel="nofollow" title="1.3.2">1.3.2</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.3.1" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.3.1" rel="nofollow" title="1.3.1">1.3.1</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.3" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.3" rel="nofollow" title="1.3">1.3</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.2.7" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.2.7" rel="nofollow" title="1.2.7">1.2.7</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.2.6" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.2.6" rel="nofollow" title="1.2.6">1.2.6</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.2.5" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.2.5" rel="nofollow" title="1.2.5">1.2.5</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.2.4" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.2.4" rel="nofollow" title="1.2.4">1.2.4</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.2.3" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.2.3" rel="nofollow" title="1.2.3">1.2.3</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.2.2" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.2.2" rel="nofollow" title="1.2.2">1.2.2</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.2.1" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.2.1" rel="nofollow" title="1.2.1">1.2.1</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.2" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.2" rel="nofollow" title="1.2">1.2</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.1.4" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.1.4" rel="nofollow" title="1.1.4">1.1.4</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.1.3" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.1.3" rel="nofollow" title="1.1.3">1.1.3</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.1.2" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.1.2" rel="nofollow" title="1.1.2">1.1.2</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.1.1" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.1.1" rel="nofollow" title="1.1.1">1.1.1</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.1" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.1" rel="nofollow" title="1.1">1.1</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.0.4" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.0.4" rel="nofollow" title="1.0.4">1.0.4</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.0.3" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.0.3" rel="nofollow" title="1.0.3">1.0.3</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.0.2" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.0.2" rel="nofollow" title="1.0.2">1.0.2</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.0.1" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.0.1" rel="nofollow" title="1.0.1">1.0.1</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.0" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.0" rel="nofollow" title="1.0">1.0</a> + </div> <!-- /.select-menu-item --> + </div> + + <div class="select-menu-no-results">Nothing to show</div> + + </div> <!-- /.select-menu-list --> + + </div> <!-- /.select-menu-modal --> + </div> <!-- /.select-menu-modal-holder --> + </div> <!-- /.select-menu --> + + </div> <!-- /.scope --> + + <ul class="tabnav-tabs"> + <li><a href="/django/django" class="tabnav-tab" highlight="repo_source">Files</a></li> + <li><a href="/django/django/commits/" class="selected tabnav-tab" highlight="repo_commits">Commits</a></li> + <li><a href="/django/django/branches" class="tabnav-tab" highlight="repo_branches" rel="nofollow">Branches <span class="counter ">37</span></a></li> + </ul> + +</div> + + + + + + + + </div> + </div><!-- /.repohead --> + + <div id="js-repo-pjax-container" class="container context-loader-container" data-pjax-container> + + + + <div class="commit full-commit "> + <a href="/django/django/tree/d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e" class="browse-button" title="Browse the code at this point in the history" rel="nofollow">Browse code</a> + + <p class="commit-title"> + Improved regex in strip_tags + </p> + + <div class="commit-desc"><pre>Thanks Pablo Recio for the report. Refs #19237.</pre></div> + + <div class="commit-meta clearfix"> + <span class="sha-block">commit <span class="sha js-selectable-text">d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e</span></span> + + <span class="sha-block" data-pjax> + 1 parent + + <a href="/django/django/commit/afa3e1633431137f4e76c7efc359b579f4d9c08e" class="sha" data-hotkey="p">afa3e16</a> + </span> + + <div class="authorship"> + <img class="gravatar" height="24" src="https://secure.gravatar.com/avatar/cf4198670f0073174b475634964b576b?s=140&d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png" width="24" /> + <span class="author-name"><a href="/claudep" rel="author">claudep</a></span> + authored <time class="js-relative-date" datetime="2013-02-06T12:20:43-08:00" title="2013-02-06 12:20:43">February 06, 2013</time> + + </div> + </div> + </div> + + <a name="diff-stat"></a> + +<div id='toc' class="details-collapse js-details-container "> + <p class="explain"> + <span class="mini-icon mini-icon-diff"></span>Showing <strong>2 changed files</strong> + with <strong>2 additions</strong> + and <strong>1 deletion</strong>. + <a href="#" class="minibutton show-diff-stats js-details-target">Show Diff Stats</a> + <a href="#" class="minibutton hide-diff-stats js-details-target">Hide Diff Stats</a></p> + + <ol class="content collapse js-transitionable"> + <li> + <span class="diffstat"> + <a href="#diff-0" class="tooltipped leftwards" title="1 addition & 1 deletion"> + 2 + <span class="diffstat-bar"> <i class='plus'></i><i class='minus'></i> </span> + </a> + </span> + <span class='mini-icon mini-icon-modified' title='modified'></span> <a href="#diff-0">django/utils/html.py</a> + </li> + <li> + <span class="diffstat"> + <a href="#diff-1" class="tooltipped leftwards" title="1 addition & 0 deletions"> + 1 + <span class="diffstat-bar"> <i class='plus'></i> </span> + </a> + </span> + <span class='mini-icon mini-icon-modified' title='modified'></span> <a href="#diff-1">tests/regressiontests/utils/html.py</a> + </li> + </ol> +</div> + + +<div id="files" class="diff-view commentable"> + <div id="diff-0" class="file js-details-container"> + <div class="meta" data-path="django/utils/html.py"> + <div class="info"> + <span class="diffstat tooltipped rightwards" title="1 addition & 1 deletion">2 <span class="diffstat-bar"><i class='plus'></i><i class='minus'></i></span></span> + + <span class="js-selectable-text css-truncate css-truncate-target" title="django/utils/html.py"> + django/utils/html.py + </span> + + </div> + <div class="actions"> + <span class="show-inline-notes"> + <label> + <input type="checkbox" checked="checked" class="js-show-inline-comments-toggle"> + show inline notes + </label> + </span> + <div class="button-group"> + <a href="/django/django/blob/d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e/django/utils/html.py" class="minibutton" rel="nofollow">View file @ <code>d7504a3</code></a> + </div> + </div> + </div> + + <div class="data highlight "> + <table class="diff-table"> + <tr id="django-utils-html-py-P0" data-position='0'> + <td id="L0L32" class="line_numbers linkable-line-number"> + <span class="line-number-content">...</span> + </td> + <td id="L0R32" class="line_numbers linkable-line-number"> + <span class="line-number-content">...</span> + </td> + <td class="gc diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=django/utils/html.py&position=0&line=32"></b> + @@ -33,7 +33,7 @@ + </td> + </tr> + <tr id="django-utils-html-py-P1" data-position='1'> + <td id="L0L33" class="line_numbers linkable-line-number"> + <span class="line-number-content">33</span> + </td> + <td id="L0R33" class="line_numbers linkable-line-number"> + <span class="line-number-content">33</span> + </td> + <td class=" diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=django/utils/html.py&position=1&line=33"></b> + html_gunk_re = re.compile(r'(?:<br clear="all">|<i><\/i>|<b><\/b>|<em><\/em>|<strong><\/strong>|<\/?smallcaps>|<\/?uppercase>)', re.IGNORECASE) + </td> + </tr> + <tr id="django-utils-html-py-P2" data-position='2'> + <td id="L0L34" class="line_numbers linkable-line-number"> + <span class="line-number-content">34</span> + </td> + <td id="L0R34" class="line_numbers linkable-line-number"> + <span class="line-number-content">34</span> + </td> + <td class=" diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=django/utils/html.py&position=2&line=34"></b> + hard_coded_bullets_re = re.compile(r'((?:<p>(?:%s).*?[a-zA-Z].*?</p>\s*)+)' % '|'.join([re.escape(x) for x in DOTS]), re.DOTALL) + </td> + </tr> + <tr id="django-utils-html-py-P3" data-position='3'> + <td id="L0L35" class="line_numbers linkable-line-number"> + <span class="line-number-content">35</span> + </td> + <td id="L0R35" class="line_numbers linkable-line-number"> + <span class="line-number-content">35</span> + </td> + <td class=" diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=django/utils/html.py&position=3&line=35"></b> + trailing_empty_content_re = re.compile(r'(?:<p>(?:&nbsp;|\s|<br \/>)*?</p>\s*)+\Z') + </td> + </tr> + <tr id="django-utils-html-py-P4" data-position='4'> + <td id="L0L36" class="line_numbers linkable-line-number"> + <span class="line-number-content">36</span> + </td> + <td id="L0R35" class="line_numbers linkable-line-number empty-cell"> + <span class="line-number-content"> </span> + </td> + <td class="gd diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=django/utils/html.py&position=4&line=36"></b> + -strip_tags_re = re.compile(r'</?\S([^=<span class="x"></span>]*=(\s*"[^"]*"|\s*\'[^\']*\'|\S*)|[^>])*?>', re.IGNORECASE) + </td> + </tr> + <tr id="django-utils-html-py-P5" data-position='5'> + <td id="L0L36" class="line_numbers linkable-line-number empty-cell"> + <span class="line-number-content"> </span> + </td> + <td id="L0R36" class="line_numbers linkable-line-number"> + <span class="line-number-content">36</span> + </td> + <td class="gi diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=django/utils/html.py&position=5&line=36"></b> + +strip_tags_re = re.compile(r'</?\S([^=<span class="x">></span>]*=(\s*"[^"]*"|\s*\'[^\']*\'|\S*)|[^>])*?>', re.IGNORECASE) + </td> + </tr> + <tr id="django-utils-html-py-P6" data-position='6'> + <td id="L0L37" class="line_numbers linkable-line-number"> + <span class="line-number-content">37</span> + </td> + <td id="L0R37" class="line_numbers linkable-line-number"> + <span class="line-number-content">37</span> + </td> + <td class=" diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=django/utils/html.py&position=6&line=37"></b> + + </td> + </tr> + <tr id="django-utils-html-py-P7" data-position='7'> + <td id="L0L38" class="line_numbers linkable-line-number"> + <span class="line-number-content">38</span> + </td> + <td id="L0R38" class="line_numbers linkable-line-number"> + <span class="line-number-content">38</span> + </td> + <td class=" diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=django/utils/html.py&position=7&line=38"></b> + + </td> + </tr> + <tr id="django-utils-html-py-P8" data-position='8'> + <td id="L0L39" class="line_numbers linkable-line-number"> + <span class="line-number-content">39</span> + </td> + <td id="L0R39" class="line_numbers linkable-line-number"> + <span class="line-number-content">39</span> + </td> + <td class=" diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=django/utils/html.py&position=8&line=39"></b> + def escape(text): + </td> + </tr> + </table> + </div> + + <div class="file-comments-place-holder" data-path="django/utils/html.py"></div> + </div> + <div id="diff-1" class="file js-details-container"> + <div class="meta" data-path="tests/regressiontests/utils/html.py"> + <div class="info"> + <span class="diffstat tooltipped rightwards" title="1 addition & 0 deletions">1 <span class="diffstat-bar"><i class='plus'></i></span></span> + + <span class="js-selectable-text css-truncate css-truncate-target" title="tests/regressiontests/utils/html.py"> + tests/regressiontests/utils/html.py + </span> + + </div> + <div class="actions"> + <span class="show-inline-notes"> + <label> + <input type="checkbox" checked="checked" class="js-show-inline-comments-toggle"> + show inline notes + </label> + </span> + <div class="button-group"> + <a href="/django/django/blob/d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e/tests/regressiontests/utils/html.py" class="minibutton" rel="nofollow">View file @ <code>d7504a3</code></a> + </div> + </div> + </div> + + <div class="data highlight "> + <table class="diff-table"> + <tr id="tests-regressiontests-utils-html-py-P0" data-position='0'> + <td id="L1L67" class="line_numbers linkable-line-number"> + <span class="line-number-content">...</span> + </td> + <td id="L1R67" class="line_numbers linkable-line-number"> + <span class="line-number-content">...</span> + </td> + <td class="gc diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=tests/regressiontests/utils/html.py&position=0&line=67"></b> + @@ -68,6 +68,7 @@ def test_strip_tags(self): + </td> + </tr> + <tr id="tests-regressiontests-utils-html-py-P1" data-position='1'> + <td id="L1L68" class="line_numbers linkable-line-number"> + <span class="line-number-content">68</span> + </td> + <td id="L1R68" class="line_numbers linkable-line-number"> + <span class="line-number-content">68</span> + </td> + <td class=" diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=tests/regressiontests/utils/html.py&position=1&line=68"></b> + ('a<p onclick="alert(\'<test>\')">b</p>c', 'abc'), + </td> + </tr> + <tr id="tests-regressiontests-utils-html-py-P2" data-position='2'> + <td id="L1L69" class="line_numbers linkable-line-number"> + <span class="line-number-content">69</span> + </td> + <td id="L1R69" class="line_numbers linkable-line-number"> + <span class="line-number-content">69</span> + </td> + <td class=" diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=tests/regressiontests/utils/html.py&position=2&line=69"></b> + ('a<p a >b</p>c', 'abc'), + </td> + </tr> + <tr id="tests-regressiontests-utils-html-py-P3" data-position='3'> + <td id="L1L70" class="line_numbers linkable-line-number"> + <span class="line-number-content">70</span> + </td> + <td id="L1R70" class="line_numbers linkable-line-number"> + <span class="line-number-content">70</span> + </td> + <td class=" diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=tests/regressiontests/utils/html.py&position=3&line=70"></b> + ('d<a:b c:d>e</p>f', 'def'), + </td> + </tr> + <tr id="tests-regressiontests-utils-html-py-P4" data-position='4'> + <td id="L1L70" class="line_numbers linkable-line-number empty-cell"> + <span class="line-number-content"> </span> + </td> + <td id="L1R71" class="line_numbers linkable-line-number"> + <span class="line-number-content">71</span> + </td> + <td class="gi diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=tests/regressiontests/utils/html.py&position=4&line=71"></b> + + ('<strong>foo</strong><a href="http://example.com">bar</a>', 'foobar'), + </td> + </tr> + <tr id="tests-regressiontests-utils-html-py-P5" data-position='5'> + <td id="L1L71" class="line_numbers linkable-line-number"> + <span class="line-number-content">71</span> + </td> + <td id="L1R72" class="line_numbers linkable-line-number"> + <span class="line-number-content">72</span> + </td> + <td class=" diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=tests/regressiontests/utils/html.py&position=5&line=72"></b> + ) + </td> + </tr> + <tr id="tests-regressiontests-utils-html-py-P6" data-position='6'> + <td id="L1L72" class="line_numbers linkable-line-number"> + <span class="line-number-content">72</span> + </td> + <td id="L1R73" class="line_numbers linkable-line-number"> + <span class="line-number-content">73</span> + </td> + <td class=" diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=tests/regressiontests/utils/html.py&position=6&line=73"></b> + for value, output in items: + </td> + </tr> + <tr id="tests-regressiontests-utils-html-py-P7" data-position='7'> + <td id="L1L73" class="line_numbers linkable-line-number"> + <span class="line-number-content">73</span> + </td> + <td id="L1R74" class="line_numbers linkable-line-number"> + <span class="line-number-content">74</span> + </td> + <td class=" diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=tests/regressiontests/utils/html.py&position=7&line=74"></b> + self.check_output(f, value, output) + </td> + </tr> + </table> + </div> + + <div class="file-comments-place-holder" data-path="tests/regressiontests/utils/html.py"></div> + </div> +</div> + + +<div id="all_commit_comments"> + + + <h2 class="commit-comments-header"> + 0 notes + on commit <code class="commit-comments-header-sha">d7504a3</code> + <span class="commit-comments-toggle-line-notes-wrapper"><label><input id="js-inline-comments-toggle" class="commit-comments-toggle-line-notes" type="checkbox">Show line notes below</label></span> + </h2> + <div id="comments" class="only-commit-comments comment-holder commit-comments"> + </div> + + <div class="discussion-bubble js-comment-container"> + <img class="discussion-bubble-avatar" height="48" src="https://secure.gravatar.com/avatar/cf4198670f0073174b475634964b576b?s=140&d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png" width="48" /> + + <form accept-charset="UTF-8" action="/django/django/commit_comment/create" method="post"><div style="margin:0;padding:0;display:inline"><input name="authenticity_token" type="hidden" value="Vbmuc30dLLFdm7POIe3xfTa4nODYc/la/wLrI1OLEOI=" /></div> + <div class="discussion-bubble-content bubble"> + <div class="discussion-bubble-inner"> + <input type='hidden' name='commit_id' value='d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e'> + + +<div class="js-previewable-comment-form previewable-comment-form write-selected" data-preview-url="/preview?repository=4164482"> + <div class="comment-form-head tabnav"> + <ul class="tabnav-tabs"> + <li><a href="#write_bucket_525" class="tabnav-tab write-tab js-write-tab selected">Write</a></li> + <li><a href="#preview_bucket_525" class="tabnav-tab preview-tab js-preview-tab">Preview</a></li> + </ul> + <span class="tabnav-right"> + <span class="tabnav-widget text">Comments are parsed with <a href="http://github.github.com/github-flavored-markdown/" class="gfm-help" target="_blank">GitHub Flavored Markdown</a></span> + </span> + </div> + <div class="comment-form-error js-comment-form-error" style="display:none;"></div> + + <div id="write_bucket_525" class="write-content js-write-bucket js-uploadable-container upload-enabled is-default" data-model="asset"> + +<a href="#fullscreen_comment_body_525" class="enable-fullscreen js-enable-fullscreen tooltipped + leftwards " title="Zen Mode"> + <span class="mini-icon mini-icon-fullscreen"></span> +</a> + + <textarea name="comment[body]" + tabindex="2" + id="comment_body_525" + placeholder="Leave a comment" + class="js-comment-field js-size-to-fit input-with-fullscreen-icon" + data-suggester="525_new_preview_suggester" + required></textarea> + + + <p class="drag-and-drop"> + <span class="default"> + Attach images by dragging & dropping them or + <input type="file" multiple="multiple" class="manual-file-chooser js-manual-file-chooser"> + <a class="manual-file-chooser-text" href="#">choose an image</a> + </span> + <span class="loading"> + <img alt="Octocat-spinner-32" height="16" src="https://a248.e.akamai.net/assets.github.com/images/spinners/octocat-spinner-32.gif?1338945075" width="16" /> Uploading your images now… + </span> + <span class="error bad-file"> + Unfortunately we don't support that file type yet. Try image files less than 5MB. + </span> + <span class="error bad-browser"> + This browser doesn't support image attachments. + </span> + <span class="error failed-request"> + Something went really wrong and we can't process that image. + </span> + </p> + + </div> + <div id="preview_bucket_525" class="preview-content js-preview-bucket"> + +<div id="openstruct-163168300" class="js-comment comment"> + <div class="comment-header normal-comment-header"> + <img class="comment-header-gravatar" height="22" src="https://secure.gravatar.com/avatar/cf4198670f0073174b475634964b576b?s=140&d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png" width="22" /> + <a href="/claudep" class="comment-header-author">claudep</a> + <span class='comment-header-action-text'> + <a href="#openstruct-163168300"> + commented + </a> + </span> + + <span class="comment-header-right"> + <a href="#openstruct-163168300" class="comment-header-date"><time class="js-relative-date" datetime="2013-04-01T06:45:26-07:00" title="2013-04-01 06:45:26">April 01, 2013</time></a> + </span> + </div> <!-- /.comment-header --> + + + <div class="comment-content"> + <div class="edit-comment-hide"> + <div class="js-comment-body comment-body markdown-body " data-body-version=""> + <p>Nothing to preview</p> + </div> + </div> + </div> <!-- /.comment-content --> +</div> <!-- /.comment --> + + </div> + + <div class="suggester-container"> + <div class="suggester js-navigation-container" id="525_new_preview_suggester" + data-url="/django/django/suggestions/commit"> + </div> + </div> +</div> + + </div> + </div> + + <div class="form-actions"> + <div class="tip"> + <img alt="Commit_comment_tip" height="35" src="https://a248.e.akamai.net/assets.github.com/images/modules/commit/commit_comment_tip.gif?1347524281" width="95" /> + <p><strong>Tip:</strong> You can also add notes to lines in a file. Hover to the left of a line to make a note</p> + </div> + <button type="submit" class="button primary" tabindex="2" data-disable-invalid data-disable-with>Comment on this commit</button> + </div> +</form> </div><!-- /.discussion-bubble --> + + + +</div> + + + +<div class="thread-subscription-status clearfix"> + <span class="mega-icon mega-icon-notifications"></span> + + <div class="select-menu js-menu-container js-select-menu"> + + <form accept-charset="UTF-8" action="/notifications/thread" data-autosubmit="true" data-remote="true" method="post"><div style="margin:0;padding:0;display:inline"><input name="authenticity_token" type="hidden" value="Vbmuc30dLLFdm7POIe3xfTa4nODYc/la/wLrI1OLEOI=" /></div> <input id="repository_id" name="repository_id" type="hidden" value="4164482" /> + <input id="thread_id" name="thread_id" type="hidden" value="d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e" /> + <input id="thread_class" name="thread_class" type="hidden" value="c" /> + + <span class="minibutton select-menu-button js-menu-target"> + <span class="js-select-button"> + <span class="mini-icon mini-icon-watching"></span> + Watch thread + </span> + </span> + + <div class="select-menu-modal-holder js-menu-content js-navigation-container"> + <div class="select-menu-modal"> + <div class="select-menu-header"> + <span class="select-menu-title">Thread notifications</span> + <span class="mini-icon mini-icon-remove-close js-menu-close"></span> + </div> <!-- /.select-menu-header --> + + <div class="select-menu-list"> + + <div class="select-menu-item js-navigation-item js-navigation-target selected"> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <div class="select-menu-item-text"> + <input checked="checked" id="id_unsubscribe" name="id" type="radio" value="unsubscribe" /> + <h4>Not watching</h4> + <span class="description">You only receive notifications for this thread if you participate or are @mentioned.</span> + <span class="js-select-button-text hidden-select-button-text"> + <span class="mini-icon mini-icon-watching"></span> + Watch thread + </span> + </div> + </div> <!-- /.select-menu-item --> + + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <div class="select-menu-item-text"> + <input checked="checked" id="id_subscribe" name="id" type="radio" value="subscribe" /> + <h4>Watching</h4> + <span class="description">Receive all notifications for this thread.</span> + <span class="js-select-button-text hidden-select-button-text"> + <span class="mini-icon mini-icon-unwatch"></span> + Unwatch thread + </span> + </div> + </div> <!-- /.select-menu-item --> + + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <div class="select-menu-item-text"> + <input checked="checked" id="id_mute" name="id" type="radio" value="mute" /> + <h4>Ignoring</h4> + <span class="description">You do not receive notifications for this thread.</span> + <span class="js-select-button-text hidden-select-button-text"> + <span class="mini-icon mini-icon-mute ignored"></span> + Stop ignoring thread + </span> + </div> + </div> <!-- /.select-menu-item --> + + </div> <!-- /.select-menu-list --> + </div> <!-- /.select-menu-modal --> + </div> <!-- /.select-menu-modal-holder --> +</form> </div> <!-- /.select-menu --> + + <p class="reason">You only receive notifications for this thread when you participate or are @mentioned.</p> +</div> + + + +<!-- COMMENTS --> +<div id='diff-comment-data' style='display:none'> +</div> + + + </div> + </div> + <div class="context-overlay"></div> + </div> + + <div id="footer-push"></div><!-- hack for sticky footer --> + </div><!-- end of wrapper - hack for sticky footer --> + + <!-- footer --> + <div id="footer"> + <div class="container clearfix"> + + <dl class="footer_nav"> + <dt>GitHub</dt> + <dd><a href="https://github.com/about">About us</a></dd> + <dd><a href="https://github.com/blog">Blog</a></dd> + <dd><a href="https://github.com/contact">Contact & support</a></dd> + <dd><a href="http://enterprise.github.com/">GitHub Enterprise</a></dd> + <dd><a href="http://status.github.com/">Site status</a></dd> + </dl> + + <dl class="footer_nav"> + <dt>Applications</dt> + <dd><a href="http://mac.github.com/">GitHub for Mac</a></dd> + <dd><a href="http://windows.github.com/">GitHub for Windows</a></dd> + <dd><a href="http://eclipse.github.com/">GitHub for Eclipse</a></dd> + <dd><a href="http://mobile.github.com/">GitHub mobile apps</a></dd> + </dl> + + <dl class="footer_nav"> + <dt>Services</dt> + <dd><a href="http://get.gaug.es/">Gauges: Web analytics</a></dd> + <dd><a href="http://speakerdeck.com">Speaker Deck: Presentations</a></dd> + <dd><a href="https://gist.github.com">Gist: Code snippets</a></dd> + <dd><a href="http://jobs.github.com/">Job board</a></dd> + </dl> + + <dl class="footer_nav"> + <dt>Documentation</dt> + <dd><a href="http://help.github.com/">GitHub Help</a></dd> + <dd><a href="http://developer.github.com/">Developer API</a></dd> + <dd><a href="http://github.github.com/github-flavored-markdown/">GitHub Flavored Markdown</a></dd> + <dd><a href="http://pages.github.com/">GitHub Pages</a></dd> + </dl> + + <dl class="footer_nav"> + <dt>More</dt> + <dd><a href="http://training.github.com/">Training</a></dd> + <dd><a href="https://github.com/edu">Students & teachers</a></dd> + <dd><a href="http://shop.github.com">The Shop</a></dd> + <dd><a href="/plans">Plans & pricing</a></dd> + <dd><a href="http://octodex.github.com/">The Octodex</a></dd> + </dl> + + <hr class="footer-divider"> + + + <p class="right">© 2013 <span title="0.09734s from fe2.rs.github.com">GitHub</span>, Inc. All rights reserved.</p> + <a class="left" href="https://github.com/"> + <span class="mega-icon mega-icon-invertocat"></span> + </a> + <ul id="legal"> + <li><a href="https://github.com/site/terms">Terms of Service</a></li> + <li><a href="https://github.com/site/privacy">Privacy</a></li> + <li><a href="https://github.com/security">Security</a></li> + </ul> + + </div><!-- /.container --> + +</div><!-- /.#footer --> + + + <div class="fullscreen-overlay js-fullscreen-overlay" id="fullscreen_overlay"> + <div class="fullscreen-container js-fullscreen-container"> + <div class="textarea-wrap"> + <textarea name="fullscreen-contents" id="fullscreen-contents" class="js-fullscreen-contents" placeholder="" data-suggester="fullscreen_suggester"></textarea> + <div class="suggester-container"> + <div class="suggester fullscreen-suggester js-navigation-container" id="fullscreen_suggester" + data-url="/django/django/suggestions/commit"> + </div> + </div> + </div> + </div> + <div class="fullscreen-sidebar"> + <a href="#" class="exit-fullscreen js-exit-fullscreen tooltipped leftwards" title="Exit Zen Mode"> + <span class="mega-icon mega-icon-normalscreen"></span> + </a> + <a href="#" class="theme-switcher js-theme-switcher tooltipped leftwards" + title="Switch themes"> + <span class="mini-icon mini-icon-brightness"></span> + </a> + </div> +</div> + + + + <div id="ajax-error-message" class="flash flash-error"> + <span class="mini-icon mini-icon-exclamation"></span> + Something went wrong with that request. Please try again. + <a href="#" class="mini-icon mini-icon-remove-close ajax-error-dismiss"></a> + </div> + + + + <span id='server_response_time' data-time='0.09780' data-host='fe2'></span> + + </body> +</html> + diff --git a/tests/utils_tests/files/strip_tags2.txt b/tests/utils_tests/files/strip_tags2.txt new file mode 100644 index 0000000000..6b2a4033c2 --- /dev/null +++ b/tests/utils_tests/files/strip_tags2.txt @@ -0,0 +1,118 @@ +_**Prerequisite**: You are already aware of the [basics of building a HelloWorld](http://developer.android.com/training/index.html) in Android and know [how to use the APIs provided in the support library](http://developer.android.com/training/basics/fragments/support-lib.html)._ + +_The code example is available on [github](http://github.com/iontech/Fragments_Example "Fragments Example")._ +_____________________________________________________________ +Ever wanted a code snippet from an Activity to be available to other activities? Perhaps a Button or a ListView, maybe a Layout or any View/ViewGroup for that matter? Fragments let us do just that. + +Necessity is the mother of invention. +Before understanding what Fragments are and how they work, we must first realize their existence in the first place. + +The Problem +----------- +Suppose we have an Android app with two Activities- [*FirstActivity*](https://github.com/iontech/Fragments_Example/blob/master/src/main/java/com/github/iontech/fragments_example/FirstActivity.java) and [*SecondActivity*](https://github.com/iontech/Fragments_Example/blob/master/src/main/java/com/github/iontech/fragments_example/SecondActivity.java). +*FirstActivity* contains two Views, a `TextView` (*textView*) and a `Button` (*button1*); and *button1* has an `onClick()` callback that `Toast`'s a simple message "Button pressed". +*SecondActivity* contains both the Views present in *FirstActivity* and a `Button` (*button2*). + +Now we want to utilize the two layout components(Views) of *FirstActivity* in *SecondActivity*, we can go about this with two approaches: + +1. Copy and Paste the xml elements of the two Views. +2. Create a separate layout for common Views and reuse it using `` layout element. + More about this [here](http://developer.android.com/training/improving-layouts/reusing-layouts.html). + +Electing the second approach makes sense cause it enables us to make reusable layouts. Everything seems great till now. We are able to make reusable layouts and use them as many times as we want. + +Now recollect that we have an `onClick()` callback assigned to *button1*. How do we reuse the same callback functionality of *button1* across multiple Activities? `` lets us reuse layouts and not the Activity source. +This is where Fragments come into play. + +Fragments +--------- +<center>  </center> +Fragments encompass both layout resource and Java source. Hence, unlike ``, they allow us to reuse the View components along with their functionality, if needed. +Fragments were first introduced in Honeycomb(API 11), living under the `android.app` package. +**Note**: API 11 implies that Fragments have no support for devices less than Honeycomb and, for the record, as of writing this post, [more than 50% of Android devices worldwide run versions of Android below Honeycomb](http://developer.android.com/about/dashboards/index.html). Developer dissapointed? You don't have to be, cause google has been cautious enough to add the Fragment APIs to the support library. Yay! + +In the support library Fragment APIs sit in the `android.support.v4.app` package. This post assumes that your `minSdk` support is below API 11. Hence we concentrate on the Fragment APIs of the support library. + +### Diving into code + +Performing code reuse with Fragments involves three major steps: + +1. Creating reusable View components - Creating a layout for the fragment. +2. Creating reusable Java source - Writing the layout's corresponding Fragment class. +3. Employing the reusable components in Activity - Making an Activity to host this Fragment. + +#### 1. Creating reusable View components +##### Creating a layout for the Fragment +This is done precisely as we do it for our activity layouts. The layout contains a root element (ViewGroup) defining the layout, For instance in our example we use a LinearLayout and its child elements(the reusable Views) that we want to have in our fragment. + +> [fragment_common.xml](https://github.com/iontech/Fragments_Example/blob/master/res/layout/fragment_common.xml) + + + + + + <button> + + + +#### 2. Creating reusable Java source +##### Writing the layout's corresponding Fragment class + +> [CommonFragment.java](https://github.com/iontech/Fragments_Example/blob/master/src/main/java/com/github/iontech/fragments_example/CommonFragment.java) + +This class will inherit `Fragment` class and must override the `onCreateView()` method. +In this method we inflate the fragment layout using the following line of code. + + View view = inflater.inflate(R.layout.fragment_common, container, false); + +*container* is the parent ViewGroup that the fragment's UI should be attached to. +Once inflation is done, we can perform various operations on the component views of the fragment. +Accessing the view elements from the layout is done exactly as we do in an Activity (using `findViewById()`) except that we use the `View` for the fragment's UI or an instance of the host Activity. + +In Activity we access Views(a Button for our example) as follows + + Button button1 = (Button) findViewById(R.id.button1); +In Fragment, we need to use the inflated view(if in `onCreateView()`) or get the instance of the host activity and access the views through this instance(when in a [lifecycle callback](http://developer.android.com/guide/components/fragments.html#Lifecycle "Fragment lifecycle callbacks") after `onCreateView()`, I generally do it in `onActivityCreated()`). + + Button button1 = (Button) view.findViewById(R.id.button1); +or + + Button button1 = (Button) getActivity().findViewById(R.id.button1); +`getActivity()` returns the instance of the Activity that is hosting this Fragment. + +Finally, in `onCreateView()` we must return the View for the fragment's UI. + + return view; + +#### 3. Employing the reusable components in Activity +##### Making an Activity to host this Fragment +This is done in two ways, statically by adding `` elements into the Activity layout or dynamically, at run time, by using `FragmentTransaction`s. +**Note**: First thing we need to ensure is that our Activity extends `FragmentActivity` class instead of the regular `Activity`. +If the `minSdk` is API 11 or higher, then we can leave our inheritance to `Activity` class and not bother about `FragmentActivity`. + +##### a. Static approach to hosting the Fragments +###### Adding `` element in activity layout + +> [activity_static.xml](https://github.com/iontech/Fragments_Example/blob/master/res/layout/activity_static.xml) + +Inorder to statically add a Fragment into your Activity, just add `` element with the necessary layout attributes and the `android:name` attribute set to the fully qualified class name of the corresponding Fragment. + + +##### b. Dynamic approach +###### Using `FragmentTransaction`s + +> [ADynamicFragmentActivity.java](https://github.com/iontech/Fragments_Example/blob/master/src/main/java/com/github/iontech/fragments_example/ADynamicFragmentActivity.java) + +Can be done using `FragmentManager` and `FragmentTransaction` classes. We call `add()`, in our FragmentActivity implementation, on an instance of `FragmentTransaction` to add a fragment to the host Activity. But that is not enough to show the fragment on the screen, i.e. the FragmentTransaction is not complete. We must call `commit()` to finish the transaction. + + CommonFragment fragment = new CommonFragment(); + FragmentManager manager = getSupportFragmentManager(); + FragmentTransaction transaction = manager.beginTransaction(); + transaction.add(R.id.dynamicFragmentLayout, fragment); + transaction.commit(); + +Similarly Fragments can be removed(`remove()`) as well as replaced(`replace()`) from the activity all at runtime. + +Congratulations, now you can use Fragments to write reusable code and easily host them over multiple activities. + +Please try again. Till next time. diff --git a/tests/utils_tests/models.py b/tests/utils_tests/models.py new file mode 100644 index 0000000000..de8f9e3803 --- /dev/null +++ b/tests/utils_tests/models.py @@ -0,0 +1,13 @@ +from django.db import models + + +class Category(models.Model): + name = models.CharField(max_length=100) + + def next(self): + return self + + +class Thing(models.Model): + name = models.CharField(max_length=100) + category = models.ForeignKey(Category) diff --git a/tests/utils_tests/test_archive.py b/tests/utils_tests/test_archive.py new file mode 100644 index 0000000000..8861b4a577 --- /dev/null +++ b/tests/utils_tests/test_archive.py @@ -0,0 +1,72 @@ +import os +import shutil +import tempfile +from django.utils import unittest + +from django.utils.archive import Archive, extract +from django.utils._os import upath + + +TEST_DIR = os.path.join(os.path.dirname(upath(__file__)), 'archives') + + +class ArchiveTester(object): + archive = None + + def setUp(self): + """ + Create temporary directory for testing extraction. + """ + self.old_cwd = os.getcwd() + self.tmpdir = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, self.tmpdir) + self.archive_path = os.path.join(TEST_DIR, self.archive) + # Always start off in TEST_DIR. + os.chdir(TEST_DIR) + + def tearDown(self): + os.chdir(self.old_cwd) + + def test_extract_method(self): + with Archive(self.archive) as archive: + archive.extract(self.tmpdir) + self.check_files(self.tmpdir) + + def test_extract_method_no_to_path(self): + os.chdir(self.tmpdir) + with Archive(self.archive_path) as archive: + archive.extract() + self.check_files(self.tmpdir) + + def test_extract_function(self): + extract(self.archive_path, self.tmpdir) + self.check_files(self.tmpdir) + + def test_extract_function_no_to_path(self): + os.chdir(self.tmpdir) + extract(self.archive_path) + self.check_files(self.tmpdir) + + def check_files(self, tmpdir): + self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, '1'))) + self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, '2'))) + self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, 'foo', '1'))) + self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, 'foo', '2'))) + self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, 'foo', 'bar', '1'))) + self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, 'foo', 'bar', '2'))) + + +class TestZip(ArchiveTester, unittest.TestCase): + archive = 'foobar.zip' + + +class TestTar(ArchiveTester, unittest.TestCase): + archive = 'foobar.tar' + + +class TestGzipTar(ArchiveTester, unittest.TestCase): + archive = 'foobar.tar.gz' + + +class TestBzip2Tar(ArchiveTester, unittest.TestCase): + archive = 'foobar.tar.bz2' diff --git a/tests/utils_tests/test_baseconv.py b/tests/utils_tests/test_baseconv.py new file mode 100644 index 0000000000..cc413b4e8e --- /dev/null +++ b/tests/utils_tests/test_baseconv.py @@ -0,0 +1,42 @@ +from unittest import TestCase +from django.utils.baseconv import base2, base16, base36, base56, base62, base64, BaseConverter +from django.utils.six.moves import xrange + +class TestBaseConv(TestCase): + + def test_baseconv(self): + nums = [-10 ** 10, 10 ** 10] + list(xrange(-100, 100)) + for converter in [base2, base16, base36, base56, base62, base64]: + for i in nums: + self.assertEqual(i, converter.decode(converter.encode(i))) + + def test_base11(self): + base11 = BaseConverter('0123456789-', sign='$') + self.assertEqual(base11.encode(1234), '-22') + self.assertEqual(base11.decode('-22'), 1234) + self.assertEqual(base11.encode(-1234), '$-22') + self.assertEqual(base11.decode('$-22'), -1234) + + def test_base20(self): + base20 = BaseConverter('0123456789abcdefghij') + self.assertEqual(base20.encode(1234), '31e') + self.assertEqual(base20.decode('31e'), 1234) + self.assertEqual(base20.encode(-1234), '-31e') + self.assertEqual(base20.decode('-31e'), -1234) + + def test_base64(self): + self.assertEqual(base64.encode(1234), 'JI') + self.assertEqual(base64.decode('JI'), 1234) + self.assertEqual(base64.encode(-1234), '$JI') + self.assertEqual(base64.decode('$JI'), -1234) + + def test_base7(self): + base7 = BaseConverter('cjdhel3', sign='g') + self.assertEqual(base7.encode(1234), 'hejd') + self.assertEqual(base7.decode('hejd'), 1234) + self.assertEqual(base7.encode(-1234), 'ghejd') + self.assertEqual(base7.decode('ghejd'), -1234) + + def test_exception(self): + self.assertRaises(ValueError, BaseConverter, 'abc', sign='a') + self.assertTrue(isinstance(BaseConverter('abc', sign='d'), BaseConverter)) diff --git a/tests/utils_tests/test_checksums.py b/tests/utils_tests/test_checksums.py new file mode 100644 index 0000000000..cee6dca2a8 --- /dev/null +++ b/tests/utils_tests/test_checksums.py @@ -0,0 +1,29 @@ +import unittest + +from django.utils import checksums + +class TestUtilsChecksums(unittest.TestCase): + + def check_output(self, function, value, output=None): + """ + Check that function(value) equals output. If output is None, + check that function(value) equals value. + """ + if output is None: + output = value + self.assertEqual(function(value), output) + + def test_luhn(self): + f = checksums.luhn + items = ( + (4111111111111111, True), ('4111111111111111', True), + (4222222222222, True), (378734493671000, True), + (5424000000000015, True), (5555555555554444, True), + (1008, True), ('0000001008', True), ('000000001008', True), + (4012888888881881, True), (1234567890123456789012345678909, True), + (4111111111211111, False), (42222222222224, False), + (100, False), ('100', False), ('0000100', False), + ('abc', False), (None, False), (object(), False), + ) + for value, output in items: + self.check_output(f, value, output) diff --git a/tests/utils_tests/test_crypto.py b/tests/utils_tests/test_crypto.py new file mode 100644 index 0000000000..5cf2934ab0 --- /dev/null +++ b/tests/utils_tests/test_crypto.py @@ -0,0 +1,146 @@ +from __future__ import unicode_literals + +import binascii +import math +import timeit +import hashlib + +from django.utils import unittest +from django.utils.crypto import constant_time_compare, pbkdf2 + + +class TestUtilsCryptoMisc(unittest.TestCase): + + def test_constant_time_compare(self): + # It's hard to test for constant time, just test the result. + self.assertTrue(constant_time_compare(b'spam', b'spam')) + self.assertFalse(constant_time_compare(b'spam', b'eggs')) + self.assertTrue(constant_time_compare('spam', 'spam')) + self.assertFalse(constant_time_compare('spam', 'eggs')) + + +class TestUtilsCryptoPBKDF2(unittest.TestCase): + + # http://tools.ietf.org/html/draft-josefsson-pbkdf2-test-vectors-06 + rfc_vectors = [ + { + "args": { + "password": "password", + "salt": "salt", + "iterations": 1, + "dklen": 20, + "digest": hashlib.sha1, + }, + "result": "0c60c80f961f0e71f3a9b524af6012062fe037a6", + }, + { + "args": { + "password": "password", + "salt": "salt", + "iterations": 2, + "dklen": 20, + "digest": hashlib.sha1, + }, + "result": "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957", + }, + { + "args": { + "password": "password", + "salt": "salt", + "iterations": 4096, + "dklen": 20, + "digest": hashlib.sha1, + }, + "result": "4b007901b765489abead49d926f721d065a429c1", + }, + # # this takes way too long :( + # { + # "args": { + # "password": "password", + # "salt": "salt", + # "iterations": 16777216, + # "dklen": 20, + # "digest": hashlib.sha1, + # }, + # "result": "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984", + # }, + { + "args": { + "password": "passwordPASSWORDpassword", + "salt": "saltSALTsaltSALTsaltSALTsaltSALTsalt", + "iterations": 4096, + "dklen": 25, + "digest": hashlib.sha1, + }, + "result": "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038", + }, + { + "args": { + "password": "pass\0word", + "salt": "sa\0lt", + "iterations": 4096, + "dklen": 16, + "digest": hashlib.sha1, + }, + "result": "56fa6aa75548099dcc37d7f03425e0c3", + }, + ] + + regression_vectors = [ + { + "args": { + "password": "password", + "salt": "salt", + "iterations": 1, + "dklen": 20, + "digest": hashlib.sha256, + }, + "result": "120fb6cffcf8b32c43e7225256c4f837a86548c9", + }, + { + "args": { + "password": "password", + "salt": "salt", + "iterations": 1, + "dklen": 20, + "digest": hashlib.sha512, + }, + "result": "867f70cf1ade02cff3752599a3a53dc4af34c7a6", + }, + { + "args": { + "password": "password", + "salt": "salt", + "iterations": 1000, + "dklen": 0, + "digest": hashlib.sha512, + }, + "result": ("afe6c5530785b6cc6b1c6453384731bd5ee432ee" + "549fd42fb6695779ad8a1c5bf59de69c48f774ef" + "c4007d5298f9033c0241d5ab69305e7b64eceeb8d" + "834cfec"), + }, + # Check leading zeros are not stripped (#17481) + { + "args": { + "password": b'\xba', + "salt": "salt", + "iterations": 1, + "dklen": 20, + "digest": hashlib.sha1, + }, + "result": '0053d3b91a7f1e54effebd6d68771e8a6e0b2c5b', + }, + ] + + def test_public_vectors(self): + for vector in self.rfc_vectors: + result = pbkdf2(**vector['args']) + self.assertEqual(binascii.hexlify(result).decode('ascii'), + vector['result']) + + def test_regression_vectors(self): + for vector in self.regression_vectors: + result = pbkdf2(**vector['args']) + self.assertEqual(binascii.hexlify(result).decode('ascii'), + vector['result']) diff --git a/tests/utils_tests/test_datastructures.py b/tests/utils_tests/test_datastructures.py new file mode 100644 index 0000000000..3161c04f97 --- /dev/null +++ b/tests/utils_tests/test_datastructures.py @@ -0,0 +1,306 @@ +""" +Tests for stuff in django.utils.datastructures. +""" + +import copy +import pickle +import warnings + +from django.test import SimpleTestCase +from django.utils.datastructures import (DictWrapper, ImmutableList, + MultiValueDict, MultiValueDictKeyError, MergeDict, SortedDict) +from django.utils import six + + +class SortedDictTests(SimpleTestCase): + def setUp(self): + self.d1 = SortedDict() + self.d1[7] = 'seven' + self.d1[1] = 'one' + self.d1[9] = 'nine' + + self.d2 = SortedDict() + self.d2[1] = 'one' + self.d2[9] = 'nine' + self.d2[0] = 'nil' + self.d2[7] = 'seven' + + def test_basic_methods(self): + self.assertEqual(list(six.iterkeys(self.d1)), [7, 1, 9]) + self.assertEqual(list(six.itervalues(self.d1)), ['seven', 'one', 'nine']) + self.assertEqual(list(six.iteritems(self.d1)), [(7, 'seven'), (1, 'one'), (9, 'nine')]) + + def test_overwrite_ordering(self): + """ Overwriting an item keeps its place. """ + self.d1[1] = 'ONE' + self.assertEqual(list(six.itervalues(self.d1)), ['seven', 'ONE', 'nine']) + + def test_append_items(self): + """ New items go to the end. """ + self.d1[0] = 'nil' + self.assertEqual(list(six.iterkeys(self.d1)), [7, 1, 9, 0]) + + def test_delete_and_insert(self): + """ + Deleting an item, then inserting the same key again will place it + at the end. + """ + del self.d2[7] + self.assertEqual(list(six.iterkeys(self.d2)), [1, 9, 0]) + self.d2[7] = 'lucky number 7' + self.assertEqual(list(six.iterkeys(self.d2)), [1, 9, 0, 7]) + + if not six.PY3: + def test_change_keys(self): + """ + Changing the keys won't do anything, it's only a copy of the + keys dict. + + This test doesn't make sense under Python 3 because keys is + an iterator. + """ + k = self.d2.keys() + k.remove(9) + self.assertEqual(self.d2.keys(), [1, 9, 0, 7]) + + def test_init_keys(self): + """ + Initialising a SortedDict with two keys will just take the first one. + + A real dict will actually take the second value so we will too, but + we'll keep the ordering from the first key found. + """ + tuples = ((2, 'two'), (1, 'one'), (2, 'second-two')) + d = SortedDict(tuples) + + self.assertEqual(list(six.iterkeys(d)), [2, 1]) + + real_dict = dict(tuples) + self.assertEqual(sorted(six.itervalues(real_dict)), ['one', 'second-two']) + + # Here the order of SortedDict values *is* what we are testing + self.assertEqual(list(six.itervalues(d)), ['second-two', 'one']) + + def test_overwrite(self): + self.d1[1] = 'not one' + self.assertEqual(self.d1[1], 'not one') + self.assertEqual(list(six.iterkeys(self.d1)), list(six.iterkeys(self.d1.copy()))) + + def test_append(self): + self.d1[13] = 'thirteen' + self.assertEqual( + repr(self.d1), + "{7: 'seven', 1: 'one', 9: 'nine', 13: 'thirteen'}" + ) + + def test_pop(self): + self.assertEqual(self.d1.pop(1, 'missing'), 'one') + self.assertEqual(self.d1.pop(1, 'missing'), 'missing') + + # We don't know which item will be popped in popitem(), so we'll + # just check that the number of keys has decreased. + l = len(self.d1) + self.d1.popitem() + self.assertEqual(l - len(self.d1), 1) + + def test_dict_equality(self): + d = SortedDict((i, i) for i in range(3)) + self.assertEqual(d, {0: 0, 1: 1, 2: 2}) + + def test_tuple_init(self): + d = SortedDict(((1, "one"), (0, "zero"), (2, "two"))) + self.assertEqual(repr(d), "{1: 'one', 0: 'zero', 2: 'two'}") + + def test_pickle(self): + self.assertEqual( + pickle.loads(pickle.dumps(self.d1, 2)), + {7: 'seven', 1: 'one', 9: 'nine'} + ) + + def test_copy(self): + orig = SortedDict(((1, "one"), (0, "zero"), (2, "two"))) + copied = copy.copy(orig) + self.assertEqual(list(six.iterkeys(orig)), [1, 0, 2]) + self.assertEqual(list(six.iterkeys(copied)), [1, 0, 2]) + + def test_clear(self): + self.d1.clear() + self.assertEqual(self.d1, {}) + self.assertEqual(self.d1.keyOrder, []) + + def test_reversed(self): + self.assertEqual(list(self.d1), [7, 1, 9]) + self.assertEqual(list(self.d2), [1, 9, 0, 7]) + self.assertEqual(list(reversed(self.d1)), [9, 1, 7]) + self.assertEqual(list(reversed(self.d2)), [7, 0, 9, 1]) + + def test_insert(self): + d = SortedDict() + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + d.insert(0, "hello", "world") + assert w[0].category is DeprecationWarning + + def test_value_for_index(self): + d = SortedDict({"a": 3}) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + self.assertEqual(d.value_for_index(0), 3) + assert w[0].category is DeprecationWarning + + +class MergeDictTests(SimpleTestCase): + + def test_simple_mergedict(self): + d1 = {'chris':'cool', 'camri':'cute', 'cotton':'adorable', + 'tulip':'snuggable', 'twoofme':'firstone'} + + d2 = {'chris2':'cool2', 'camri2':'cute2', 'cotton2':'adorable2', + 'tulip2':'snuggable2'} + + d3 = {'chris3':'cool3', 'camri3':'cute3', 'cotton3':'adorable3', + 'tulip3':'snuggable3'} + + d4 = {'twoofme': 'secondone'} + + md = MergeDict(d1, d2, d3) + + self.assertEqual(md['chris'], 'cool') + self.assertEqual(md['camri'], 'cute') + self.assertEqual(md['twoofme'], 'firstone') + + md2 = md.copy() + self.assertEqual(md2['chris'], 'cool') + + def test_mergedict_merges_multivaluedict(self): + """ MergeDict can merge MultiValueDicts """ + + multi1 = MultiValueDict({'key1': ['value1'], + 'key2': ['value2', 'value3']}) + + multi2 = MultiValueDict({'key2': ['value4'], + 'key4': ['value5', 'value6']}) + + mm = MergeDict(multi1, multi2) + + # Although 'key2' appears in both dictionaries, + # only the first value is used. + self.assertEqual(mm.getlist('key2'), ['value2', 'value3']) + self.assertEqual(mm.getlist('key4'), ['value5', 'value6']) + self.assertEqual(mm.getlist('undefined'), []) + + self.assertEqual(sorted(six.iterkeys(mm)), ['key1', 'key2', 'key4']) + self.assertEqual(len(list(six.itervalues(mm))), 3) + + self.assertTrue('value1' in six.itervalues(mm)) + + self.assertEqual(sorted(six.iteritems(mm), key=lambda k: k[0]), + [('key1', 'value1'), ('key2', 'value3'), + ('key4', 'value6')]) + + self.assertEqual([(k,mm.getlist(k)) for k in sorted(mm)], + [('key1', ['value1']), + ('key2', ['value2', 'value3']), + ('key4', ['value5', 'value6'])]) + +class MultiValueDictTests(SimpleTestCase): + + def test_multivaluedict(self): + d = MultiValueDict({'name': ['Adrian', 'Simon'], + 'position': ['Developer']}) + + self.assertEqual(d['name'], 'Simon') + self.assertEqual(d.get('name'), 'Simon') + self.assertEqual(d.getlist('name'), ['Adrian', 'Simon']) + self.assertEqual(sorted(list(six.iteritems(d))), + [('name', 'Simon'), ('position', 'Developer')]) + + self.assertEqual(sorted(list(six.iterlists(d))), + [('name', ['Adrian', 'Simon']), + ('position', ['Developer'])]) + + # MultiValueDictKeyError: "Key 'lastname' not found in + # <MultiValueDict: {'position': ['Developer'], + # 'name': ['Adrian', 'Simon']}>" + six.assertRaisesRegex(self, MultiValueDictKeyError, + r'"Key \'lastname\' not found in <MultiValueDict', + d.__getitem__, 'lastname') + + self.assertEqual(d.get('lastname'), None) + self.assertEqual(d.get('lastname', 'nonexistent'), 'nonexistent') + self.assertEqual(d.getlist('lastname'), []) + self.assertEqual(d.getlist('doesnotexist', ['Adrian', 'Simon']), + ['Adrian', 'Simon']) + + d.setlist('lastname', ['Holovaty', 'Willison']) + self.assertEqual(d.getlist('lastname'), ['Holovaty', 'Willison']) + self.assertEqual(sorted(list(six.itervalues(d))), + ['Developer', 'Simon', 'Willison']) + + def test_appendlist(self): + d = MultiValueDict() + d.appendlist('name', 'Adrian') + d.appendlist('name', 'Simon') + self.assertEqual(d.getlist('name'), ['Adrian', 'Simon']) + + def test_copy(self): + for copy_func in [copy.copy, lambda d: d.copy()]: + d1 = MultiValueDict({ + "developers": ["Carl", "Fred"] + }) + self.assertEqual(d1["developers"], "Fred") + d2 = copy_func(d1) + d2.update({"developers": "Groucho"}) + self.assertEqual(d2["developers"], "Groucho") + self.assertEqual(d1["developers"], "Fred") + + d1 = MultiValueDict({ + "key": [[]] + }) + self.assertEqual(d1["key"], []) + d2 = copy_func(d1) + d2["key"].append("Penguin") + self.assertEqual(d1["key"], ["Penguin"]) + self.assertEqual(d2["key"], ["Penguin"]) + + def test_dict_translation(self): + mvd = MultiValueDict({ + 'devs': ['Bob', 'Joe'], + 'pm': ['Rory'], + }) + d = mvd.dict() + self.assertEqual(list(six.iterkeys(d)), list(six.iterkeys(mvd))) + for key in six.iterkeys(mvd): + self.assertEqual(d[key], mvd[key]) + + self.assertEqual({}, MultiValueDict().dict()) + + +class ImmutableListTests(SimpleTestCase): + + def test_sort(self): + d = ImmutableList(range(10)) + + # AttributeError: ImmutableList object is immutable. + self.assertRaisesMessage(AttributeError, + 'ImmutableList object is immutable.', d.sort) + + self.assertEqual(repr(d), '(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)') + + def test_custom_warning(self): + d = ImmutableList(range(10), warning="Object is immutable!") + + self.assertEqual(d[1], 1) + + # AttributeError: Object is immutable! + self.assertRaisesMessage(AttributeError, + 'Object is immutable!', d.__setitem__, 1, 'test') + + +class DictWrapperTests(SimpleTestCase): + + def test_dictwrapper(self): + f = lambda x: "*%s" % x + d = DictWrapper({'a': 'a'}, f, 'xx_') + self.assertEqual("Normal: %(a)s. Modified: %(xx_a)s" % d, + 'Normal: a. Modified: *a') diff --git a/tests/utils_tests/test_dateformat.py b/tests/utils_tests/test_dateformat.py new file mode 100644 index 0000000000..78cedaf764 --- /dev/null +++ b/tests/utils_tests/test_dateformat.py @@ -0,0 +1,144 @@ +from __future__ import unicode_literals + +from datetime import datetime, date +import os +import time + +from django.utils.dateformat import format +from django.utils import dateformat, translation, unittest +from django.utils.timezone import utc +from django.utils.tzinfo import FixedOffset, LocalTimezone + + +class DateFormatTests(unittest.TestCase): + def setUp(self): + self.old_TZ = os.environ.get('TZ') + os.environ['TZ'] = 'Europe/Copenhagen' + self._orig_lang = translation.get_language() + translation.activate('en-us') + + try: + # Check if a timezone has been set + time.tzset() + self.tz_tests = True + except AttributeError: + # No timezone available. Don't run the tests that require a TZ + self.tz_tests = False + + def tearDown(self): + translation.activate(self._orig_lang) + if self.old_TZ is None: + del os.environ['TZ'] + else: + os.environ['TZ'] = self.old_TZ + + # Cleanup - force re-evaluation of TZ environment variable. + if self.tz_tests: + time.tzset() + + def test_date(self): + d = date(2009, 5, 16) + self.assertEqual(date.fromtimestamp(int(format(d, 'U'))), d) + + def test_naive_datetime(self): + dt = datetime(2009, 5, 16, 5, 30, 30) + self.assertEqual(datetime.fromtimestamp(int(format(dt, 'U'))), dt) + + def test_datetime_with_local_tzinfo(self): + ltz = LocalTimezone(datetime.now()) + dt = datetime(2009, 5, 16, 5, 30, 30, tzinfo=ltz) + self.assertEqual(datetime.fromtimestamp(int(format(dt, 'U')), ltz), dt) + self.assertEqual(datetime.fromtimestamp(int(format(dt, 'U'))), dt.replace(tzinfo=None)) + + def test_datetime_with_tzinfo(self): + tz = FixedOffset(-510) + ltz = LocalTimezone(datetime.now()) + dt = datetime(2009, 5, 16, 5, 30, 30, tzinfo=tz) + self.assertEqual(datetime.fromtimestamp(int(format(dt, 'U')), tz), dt) + self.assertEqual(datetime.fromtimestamp(int(format(dt, 'U')), ltz), dt) + self.assertEqual(datetime.fromtimestamp(int(format(dt, 'U'))), dt.astimezone(ltz).replace(tzinfo=None)) + self.assertEqual(datetime.fromtimestamp(int(format(dt, 'U')), tz).utctimetuple(), dt.utctimetuple()) + self.assertEqual(datetime.fromtimestamp(int(format(dt, 'U')), ltz).utctimetuple(), dt.utctimetuple()) + + def test_epoch(self): + udt = datetime(1970, 1, 1, tzinfo=utc) + self.assertEqual(format(udt, 'U'), '0') + + def test_empty_format(self): + my_birthday = datetime(1979, 7, 8, 22, 00) + + self.assertEqual(dateformat.format(my_birthday, ''), '') + + def test_am_pm(self): + my_birthday = datetime(1979, 7, 8, 22, 00) + + self.assertEqual(dateformat.format(my_birthday, 'a'), 'p.m.') + + def test_microsecond(self): + # Regression test for #18951 + dt = datetime(2009, 5, 16, microsecond=123) + self.assertEqual(dateformat.format(dt, 'u'), '000123') + + def test_date_formats(self): + my_birthday = datetime(1979, 7, 8, 22, 00) + timestamp = datetime(2008, 5, 19, 11, 45, 23, 123456) + + self.assertEqual(dateformat.format(my_birthday, 'A'), 'PM') + self.assertEqual(dateformat.format(timestamp, 'c'), '2008-05-19T11:45:23.123456') + self.assertEqual(dateformat.format(my_birthday, 'd'), '08') + self.assertEqual(dateformat.format(my_birthday, 'j'), '8') + self.assertEqual(dateformat.format(my_birthday, 'l'), 'Sunday') + self.assertEqual(dateformat.format(my_birthday, 'L'), 'False') + self.assertEqual(dateformat.format(my_birthday, 'm'), '07') + self.assertEqual(dateformat.format(my_birthday, 'M'), 'Jul') + self.assertEqual(dateformat.format(my_birthday, 'b'), 'jul') + self.assertEqual(dateformat.format(my_birthday, 'n'), '7') + self.assertEqual(dateformat.format(my_birthday, 'N'), 'July') + + def test_time_formats(self): + my_birthday = datetime(1979, 7, 8, 22, 00) + + self.assertEqual(dateformat.format(my_birthday, 'P'), '10 p.m.') + self.assertEqual(dateformat.format(my_birthday, 's'), '00') + self.assertEqual(dateformat.format(my_birthday, 'S'), 'th') + self.assertEqual(dateformat.format(my_birthday, 't'), '31') + self.assertEqual(dateformat.format(my_birthday, 'w'), '0') + self.assertEqual(dateformat.format(my_birthday, 'W'), '27') + self.assertEqual(dateformat.format(my_birthday, 'y'), '79') + self.assertEqual(dateformat.format(my_birthday, 'Y'), '1979') + self.assertEqual(dateformat.format(my_birthday, 'z'), '189') + + def test_dateformat(self): + my_birthday = datetime(1979, 7, 8, 22, 00) + + self.assertEqual(dateformat.format(my_birthday, r'Y z \C\E\T'), '1979 189 CET') + + self.assertEqual(dateformat.format(my_birthday, r'jS \o\f F'), '8th of July') + + def test_futuredates(self): + the_future = datetime(2100, 10, 25, 0, 00) + self.assertEqual(dateformat.format(the_future, r'Y'), '2100') + + def test_timezones(self): + my_birthday = datetime(1979, 7, 8, 22, 00) + summertime = datetime(2005, 10, 30, 1, 00) + wintertime = datetime(2005, 10, 30, 4, 00) + timestamp = datetime(2008, 5, 19, 11, 45, 23, 123456) + + if self.tz_tests: + self.assertEqual(dateformat.format(my_birthday, 'O'), '+0100') + self.assertEqual(dateformat.format(my_birthday, 'r'), 'Sun, 8 Jul 1979 22:00:00 +0100') + self.assertEqual(dateformat.format(my_birthday, 'T'), 'CET') + self.assertEqual(dateformat.format(my_birthday, 'U'), '300315600') + self.assertEqual(dateformat.format(timestamp, 'u'), '123456') + self.assertEqual(dateformat.format(my_birthday, 'Z'), '3600') + self.assertEqual(dateformat.format(summertime, 'I'), '1') + self.assertEqual(dateformat.format(summertime, 'O'), '+0200') + self.assertEqual(dateformat.format(wintertime, 'I'), '0') + self.assertEqual(dateformat.format(wintertime, 'O'), '+0100') + + # Ticket #16924 -- We don't need timezone support to test this + # 3h30m to the west of UTC + tz = FixedOffset(-3*60 - 30) + dt = datetime(2009, 5, 16, 5, 30, 30, tzinfo=tz) + self.assertEqual(dateformat.format(dt, 'O'), '-0330') diff --git a/tests/utils_tests/test_dateparse.py b/tests/utils_tests/test_dateparse.py new file mode 100644 index 0000000000..1a6ca646b8 --- /dev/null +++ b/tests/utils_tests/test_dateparse.py @@ -0,0 +1,44 @@ +from __future__ import unicode_literals + +from datetime import date, time, datetime + +from django.utils.dateparse import parse_date, parse_time, parse_datetime +from django.utils import unittest +from django.utils.tzinfo import FixedOffset + + +class DateParseTests(unittest.TestCase): + + def test_parse_date(self): + # Valid inputs + self.assertEqual(parse_date('2012-04-23'), date(2012, 4, 23)) + self.assertEqual(parse_date('2012-4-9'), date(2012, 4, 9)) + # Invalid inputs + self.assertEqual(parse_date('20120423'), None) + self.assertRaises(ValueError, parse_date, '2012-04-56') + + def test_parse_time(self): + # Valid inputs + self.assertEqual(parse_time('09:15:00'), time(9, 15)) + self.assertEqual(parse_time('10:10'), time(10, 10)) + self.assertEqual(parse_time('10:20:30.400'), time(10, 20, 30, 400000)) + self.assertEqual(parse_time('4:8:16'), time(4, 8, 16)) + # Invalid inputs + self.assertEqual(parse_time('091500'), None) + self.assertRaises(ValueError, parse_time, '09:15:90') + + def test_parse_datetime(self): + # Valid inputs + self.assertEqual(parse_datetime('2012-04-23T09:15:00'), + datetime(2012, 4, 23, 9, 15)) + self.assertEqual(parse_datetime('2012-4-9 4:8:16'), + datetime(2012, 4, 9, 4, 8, 16)) + self.assertEqual(parse_datetime('2012-04-23T09:15:00Z'), + datetime(2012, 4, 23, 9, 15, 0, 0, FixedOffset(0))) + self.assertEqual(parse_datetime('2012-4-9 4:8:16-0320'), + datetime(2012, 4, 9, 4, 8, 16, 0, FixedOffset(-200))) + self.assertEqual(parse_datetime('2012-04-23T10:20:30.400+02:30'), + datetime(2012, 4, 23, 10, 20, 30, 400000, FixedOffset(150))) + # Invalid inputs + self.assertEqual(parse_datetime('20120423091500'), None) + self.assertRaises(ValueError, parse_datetime, '2012-04-56T09:15:90') diff --git a/tests/utils_tests/test_datetime_safe.py b/tests/utils_tests/test_datetime_safe.py new file mode 100644 index 0000000000..c9c34dbf40 --- /dev/null +++ b/tests/utils_tests/test_datetime_safe.py @@ -0,0 +1,42 @@ +import unittest + +from datetime import date as original_date, datetime as original_datetime +from django.utils.datetime_safe import date, datetime + +class DatetimeTests(unittest.TestCase): + + def setUp(self): + self.just_safe = (1900, 1, 1) + self.just_unsafe = (1899, 12, 31, 23, 59, 59) + self.really_old = (20, 1, 1) + self.more_recent = (2006, 1, 1) + + def test_compare_datetimes(self): + self.assertEqual(original_datetime(*self.more_recent), datetime(*self.more_recent)) + self.assertEqual(original_datetime(*self.really_old), datetime(*self.really_old)) + self.assertEqual(original_date(*self.more_recent), date(*self.more_recent)) + self.assertEqual(original_date(*self.really_old), date(*self.really_old)) + + self.assertEqual(original_date(*self.just_safe).strftime('%Y-%m-%d'), date(*self.just_safe).strftime('%Y-%m-%d')) + self.assertEqual(original_datetime(*self.just_safe).strftime('%Y-%m-%d'), datetime(*self.just_safe).strftime('%Y-%m-%d')) + + def test_safe_strftime(self): + self.assertEqual(date(*self.just_unsafe[:3]).strftime('%Y-%m-%d (weekday %w)'), '1899-12-31 (weekday 0)') + self.assertEqual(date(*self.just_safe).strftime('%Y-%m-%d (weekday %w)'), '1900-01-01 (weekday 1)') + + self.assertEqual(datetime(*self.just_unsafe).strftime('%Y-%m-%d %H:%M:%S (weekday %w)'), '1899-12-31 23:59:59 (weekday 0)') + self.assertEqual(datetime(*self.just_safe).strftime('%Y-%m-%d %H:%M:%S (weekday %w)'), '1900-01-01 00:00:00 (weekday 1)') + + # %y will error before this date + self.assertEqual(date(*self.just_safe).strftime('%y'), '00') + self.assertEqual(datetime(*self.just_safe).strftime('%y'), '00') + + self.assertEqual(date(1850, 8, 2).strftime("%Y/%m/%d was a %A"), '1850/08/02 was a Friday') + + def test_zero_padding(self): + """ + Regression for #12524 + + Check that pre-1000AD dates are padded with zeros if necessary + """ + self.assertEqual(date(1, 1, 1).strftime("%Y/%m/%d was a %A"), '0001/01/01 was a Monday') diff --git a/tests/utils_tests/test_decorators.py b/tests/utils_tests/test_decorators.py new file mode 100644 index 0000000000..4d503df026 --- /dev/null +++ b/tests/utils_tests/test_decorators.py @@ -0,0 +1,108 @@ +from django.http import HttpResponse +from django.template import Template, Context +from django.template.response import TemplateResponse +from django.test import TestCase, RequestFactory +from django.utils.decorators import decorator_from_middleware + + +class ProcessViewMiddleware(object): + def process_view(self, request, view_func, view_args, view_kwargs): + pass + +process_view_dec = decorator_from_middleware(ProcessViewMiddleware) + +@process_view_dec +def process_view(request): + return HttpResponse() + + +class ClassProcessView(object): + def __call__(self, request): + return HttpResponse() + +class_process_view = process_view_dec(ClassProcessView()) + + +class FullMiddleware(object): + def process_request(self, request): + request.process_request_reached = True + + def process_view(sef, request, view_func, view_args, view_kwargs): + request.process_view_reached = True + + def process_template_response(self, request, response): + request.process_template_response_reached = True + return response + + def process_response(self, request, response): + # This should never receive unrendered content. + request.process_response_content = response.content + request.process_response_reached = True + return response + +full_dec = decorator_from_middleware(FullMiddleware) + + +class DecoratorFromMiddlewareTests(TestCase): + """ + Tests for view decorators created using + ``django.utils.decorators.decorator_from_middleware``. + """ + rf = RequestFactory() + + def test_process_view_middleware(self): + """ + Test a middleware that implements process_view. + """ + process_view(self.rf.get('/')) + + def test_callable_process_view_middleware(self): + """ + Test a middleware that implements process_view, operating on a callable class. + """ + class_process_view(self.rf.get('/')) + + def test_full_dec_normal(self): + """ + Test that all methods of middleware are called for normal HttpResponses + """ + + @full_dec + def normal_view(request): + t = Template("Hello world") + return HttpResponse(t.render(Context({}))) + + request = self.rf.get('/') + response = normal_view(request) + self.assertTrue(getattr(request, 'process_request_reached', False)) + self.assertTrue(getattr(request, 'process_view_reached', False)) + # process_template_response must not be called for HttpResponse + self.assertFalse(getattr(request, 'process_template_response_reached', False)) + self.assertTrue(getattr(request, 'process_response_reached', False)) + + def test_full_dec_templateresponse(self): + """ + Test that all methods of middleware are called for TemplateResponses in + the right sequence. + """ + + @full_dec + def template_response_view(request): + t = Template("Hello world") + return TemplateResponse(request, t, {}) + + request = self.rf.get('/') + response = template_response_view(request) + self.assertTrue(getattr(request, 'process_request_reached', False)) + self.assertTrue(getattr(request, 'process_view_reached', False)) + self.assertTrue(getattr(request, 'process_template_response_reached', False)) + # response must not be rendered yet. + self.assertFalse(response._is_rendered) + # process_response must not be called until after response is rendered, + # otherwise some decorators like csrf_protect and gzip_page will not + # work correctly. See #16004 + self.assertFalse(getattr(request, 'process_response_reached', False)) + response.render() + self.assertTrue(getattr(request, 'process_response_reached', False)) + # Check that process_response saw the rendered content + self.assertEqual(request.process_response_content, b"Hello world") diff --git a/tests/utils_tests/test_encoding.py b/tests/utils_tests/test_encoding.py new file mode 100644 index 0000000000..7aaba25a7a --- /dev/null +++ b/tests/utils_tests/test_encoding.py @@ -0,0 +1,23 @@ +# -*- encoding: utf-8 -*- +from __future__ import unicode_literals + +from django.utils import unittest +from django.utils.encoding import force_bytes, filepath_to_uri + + +class TestEncodingUtils(unittest.TestCase): + def test_force_bytes_exception(self): + """ + Test that force_bytes knows how to convert to bytes an exception + containing non-ASCII characters in its args. + """ + error_msg = "This is an exception, voilà" + exc = ValueError(error_msg) + result = force_bytes(exc) + self.assertEqual(result, error_msg.encode('utf-8')) + + def test_filepath_to_uri(self): + self.assertEqual(filepath_to_uri('upload\\чубака.mp4'), + 'upload/%D1%87%D1%83%D0%B1%D0%B0%D0%BA%D0%B0.mp4') + self.assertEqual(filepath_to_uri('upload\\чубака.mp4'.encode('utf-8')), + 'upload/%D1%87%D1%83%D0%B1%D0%B0%D0%BA%D0%B0.mp4') diff --git a/tests/utils_tests/test_feedgenerator.py b/tests/utils_tests/test_feedgenerator.py new file mode 100644 index 0000000000..bcd53bb2a0 --- /dev/null +++ b/tests/utils_tests/test_feedgenerator.py @@ -0,0 +1,117 @@ +from __future__ import unicode_literals + +import datetime + +from django.utils import feedgenerator, tzinfo, unittest + +class FeedgeneratorTest(unittest.TestCase): + """ + Tests for the low-level syndication feed framework. + """ + + def test_get_tag_uri(self): + """ + Test get_tag_uri() correctly generates TagURIs. + """ + self.assertEqual( + feedgenerator.get_tag_uri('http://example.org/foo/bar#headline', datetime.date(2004, 10, 25)), + 'tag:example.org,2004-10-25:/foo/bar/headline') + + def test_get_tag_uri_with_port(self): + """ + Test that get_tag_uri() correctly generates TagURIs from URLs with port + numbers. + """ + self.assertEqual( + feedgenerator.get_tag_uri('http://www.example.org:8000/2008/11/14/django#headline', datetime.datetime(2008, 11, 14, 13, 37, 0)), + 'tag:www.example.org,2008-11-14:/2008/11/14/django/headline') + + def test_rfc2822_date(self): + """ + Test rfc2822_date() correctly formats datetime objects. + """ + self.assertEqual( + feedgenerator.rfc2822_date(datetime.datetime(2008, 11, 14, 13, 37, 0)), + "Fri, 14 Nov 2008 13:37:00 -0000" + ) + + def test_rfc2822_date_with_timezone(self): + """ + Test rfc2822_date() correctly formats datetime objects with tzinfo. + """ + self.assertEqual( + feedgenerator.rfc2822_date(datetime.datetime(2008, 11, 14, 13, 37, 0, tzinfo=tzinfo.FixedOffset(datetime.timedelta(minutes=60)))), + "Fri, 14 Nov 2008 13:37:00 +0100" + ) + + def test_rfc2822_date_without_time(self): + """ + Test rfc2822_date() correctly formats date objects. + """ + self.assertEqual( + feedgenerator.rfc2822_date(datetime.date(2008, 11, 14)), + "Fri, 14 Nov 2008 00:00:00 -0000" + ) + + def test_rfc3339_date(self): + """ + Test rfc3339_date() correctly formats datetime objects. + """ + self.assertEqual( + feedgenerator.rfc3339_date(datetime.datetime(2008, 11, 14, 13, 37, 0)), + "2008-11-14T13:37:00Z" + ) + + def test_rfc3339_date_with_timezone(self): + """ + Test rfc3339_date() correctly formats datetime objects with tzinfo. + """ + self.assertEqual( + feedgenerator.rfc3339_date(datetime.datetime(2008, 11, 14, 13, 37, 0, tzinfo=tzinfo.FixedOffset(datetime.timedelta(minutes=120)))), + "2008-11-14T13:37:00+02:00" + ) + + def test_rfc3339_date_without_time(self): + """ + Test rfc3339_date() correctly formats date objects. + """ + self.assertEqual( + feedgenerator.rfc3339_date(datetime.date(2008, 11, 14)), + "2008-11-14T00:00:00Z" + ) + + def test_atom1_mime_type(self): + """ + Test to make sure Atom MIME type has UTF8 Charset parameter set + """ + atom_feed = feedgenerator.Atom1Feed("title", "link", "description") + self.assertEqual( + atom_feed.mime_type, "application/atom+xml; charset=utf-8" + ) + + def test_rss_mime_type(self): + """ + Test to make sure RSS MIME type has UTF8 Charset parameter set + """ + rss_feed = feedgenerator.Rss201rev2Feed("title", "link", "description") + self.assertEqual( + rss_feed.mime_type, "application/rss+xml; charset=utf-8" + ) + + # Two regression tests for #14202 + + def test_feed_without_feed_url_gets_rendered_without_atom_link(self): + feed = feedgenerator.Rss201rev2Feed('title', '/link/', 'descr') + self.assertEqual(feed.feed['feed_url'], None) + feed_content = feed.writeString('utf-8') + self.assertNotIn('<atom:link', feed_content) + self.assertNotIn('href="/feed/"', feed_content) + self.assertNotIn('rel="self"', feed_content) + + def test_feed_with_feed_url_gets_rendered_with_atom_link(self): + feed = feedgenerator.Rss201rev2Feed('title', '/link/', 'descr', feed_url='/feed/') + self.assertEqual(feed.feed['feed_url'], '/feed/') + feed_content = feed.writeString('utf-8') + self.assertIn('<atom:link', feed_content) + self.assertIn('href="/feed/"', feed_content) + self.assertIn('rel="self"', feed_content) diff --git a/tests/utils_tests/test_functional.py b/tests/utils_tests/test_functional.py new file mode 100644 index 0000000000..3bb50007c6 --- /dev/null +++ b/tests/utils_tests/test_functional.py @@ -0,0 +1,66 @@ +from django.utils import unittest +from django.utils.functional import lazy, lazy_property, cached_property + + +class FunctionalTestCase(unittest.TestCase): + def test_lazy(self): + t = lazy(lambda: tuple(range(3)), list, tuple) + for a, b in zip(t(), range(3)): + self.assertEqual(a, b) + + def test_lazy_base_class(self): + """Test that lazy also finds base class methods in the proxy object""" + + class Base(object): + def base_method(self): + pass + + class Klazz(Base): + pass + + t = lazy(lambda: Klazz(), Klazz)() + self.assertTrue('base_method' in dir(t)) + + def test_lazy_property(self): + + class A(object): + + def _get_do(self): + raise NotImplementedError + def _set_do(self, value): + raise NotImplementedError + do = lazy_property(_get_do, _set_do) + + class B(A): + def _get_do(self): + return "DO IT" + + self.assertRaises(NotImplementedError, lambda: A().do) + self.assertEqual(B().do, 'DO IT') + + def test_cached_property(self): + """ + Test that cached_property caches its value, + and that it behaves like a property + """ + + class A(object): + + @cached_property + def value(self): + return 1, object() + + a = A() + + # check that it is cached + self.assertEqual(a.value, a.value) + + # check that it returns the right thing + self.assertEqual(a.value[0], 1) + + # check that state isn't shared between instances + a2 = A() + self.assertNotEqual(a.value, a2.value) + + # check that it behaves like a property when there's no instance + self.assertIsInstance(A.value, cached_property) diff --git a/tests/utils_tests/test_html.py b/tests/utils_tests/test_html.py new file mode 100644 index 0000000000..090cc32d1c --- /dev/null +++ b/tests/utils_tests/test_html.py @@ -0,0 +1,176 @@ +from __future__ import unicode_literals + +from datetime import datetime +import os + +from django.utils import html +from django.utils._os import upath +from django.utils.unittest import TestCase + + +class TestUtilsHtml(TestCase): + + def check_output(self, function, value, output=None): + """ + Check that function(value) equals output. If output is None, + check that function(value) equals value. + """ + if output is None: + output = value + self.assertEqual(function(value), output) + + def test_escape(self): + f = html.escape + items = ( + ('&','&'), + ('<', '<'), + ('>', '>'), + ('"', '"'), + ("'", '''), + ) + # Substitution patterns for testing the above items. + patterns = ("%s", "asdf%sfdsa", "%s1", "1%sb") + for value, output in items: + for pattern in patterns: + self.check_output(f, pattern % value, pattern % output) + # Check repeated values. + self.check_output(f, value * 2, output * 2) + # Verify it doesn't double replace &. + self.check_output(f, '<&', '<&') + + def test_format_html(self): + self.assertEqual( + html.format_html("{0} {1} {third} {fourth}", + "< Dangerous >", + html.mark_safe("<b>safe</b>"), + third="< dangerous again", + fourth=html.mark_safe("<i>safe again</i>") + ), + "< Dangerous > <b>safe</b> < dangerous again <i>safe again</i>" + ) + + def test_linebreaks(self): + f = html.linebreaks + items = ( + ("para1\n\npara2\r\rpara3", "<p>para1</p>\n\n<p>para2</p>\n\n<p>para3</p>"), + ("para1\nsub1\rsub2\n\npara2", "<p>para1<br />sub1<br />sub2</p>\n\n<p>para2</p>"), + ("para1\r\n\r\npara2\rsub1\r\rpara4", "<p>para1</p>\n\n<p>para2<br />sub1</p>\n\n<p>para4</p>"), + ("para1\tmore\n\npara2", "<p>para1\tmore</p>\n\n<p>para2</p>"), + ) + for value, output in items: + self.check_output(f, value, output) + + def test_strip_tags(self): + f = html.strip_tags + items = ( + ('<adf>a', 'a'), + ('</adf>a', 'a'), + ('<asdf><asdf>e', 'e'), + ('<f', '<f'), + ('</fe', '</fe'), + ('<x>b<y>', 'b'), + ('a<p onclick="alert(\'<test>\')">b</p>c', 'abc'), + ('a<p a >b</p>c', 'abc'), + ('d<a:b c:d>e</p>f', 'def'), + ('<strong>foo</strong><a href="http://example.com">bar</a>', 'foobar'), + ) + for value, output in items: + self.check_output(f, value, output) + + # Test with more lengthy content (also catching performance regressions) + for filename in ('strip_tags1.html', 'strip_tags2.txt'): + path = os.path.join(os.path.dirname(upath(__file__)), 'files', filename) + with open(path, 'r') as fp: + start = datetime.now() + stripped = html.strip_tags(fp.read()) + elapsed = datetime.now() - start + self.assertEqual(elapsed.seconds, 0) + self.assertIn("Please try again.", stripped) + self.assertNotIn('<', stripped) + + def test_strip_spaces_between_tags(self): + f = html.strip_spaces_between_tags + # Strings that should come out untouched. + items = (' <adf>', '<adf> ', ' </adf> ', ' <f> x</f>') + for value in items: + self.check_output(f, value) + # Strings that have spaces to strip. + items = ( + ('<d> </d>', '<d></d>'), + ('<p>hello </p>\n<p> world</p>', '<p>hello </p><p> world</p>'), + ('\n<p>\t</p>\n<p> </p>\n', '\n<p></p><p></p>\n'), + ) + for value, output in items: + self.check_output(f, value, output) + + def test_strip_entities(self): + f = html.strip_entities + # Strings that should come out untouched. + values = ("&", "&a", "&a", "a&#a") + for value in values: + self.check_output(f, value) + # Valid entities that should be stripped from the patterns. + entities = ("", "", "&a;", "&fdasdfasdfasdf;") + patterns = ( + ("asdf %(entity)s ", "asdf "), + ("%(entity)s%(entity)s", ""), + ("&%(entity)s%(entity)s", "&"), + ("%(entity)s3", "3"), + ) + for entity in entities: + for in_pattern, output in patterns: + self.check_output(f, in_pattern % {'entity': entity}, output) + + def test_fix_ampersands(self): + f = html.fix_ampersands + # Strings without ampersands or with ampersands already encoded. + values = ("a", "b", "&a;", "& &x; ", "asdf") + patterns = ( + ("%s", "%s"), + ("&%s", "&%s"), + ("&%s&", "&%s&"), + ) + for value in values: + for in_pattern, out_pattern in patterns: + self.check_output(f, in_pattern % value, out_pattern % value) + # Strings with ampersands that need encoding. + items = ( + ("&#;", "&#;"), + ("ͫ ;", "&#875 ;"), + ("abc;", "&#4abc;"), + ) + for value, output in items: + self.check_output(f, value, output) + + def test_escapejs(self): + f = html.escapejs + items = ( + ('"double quotes" and \'single quotes\'', '\\u0022double quotes\\u0022 and \\u0027single quotes\\u0027'), + (r'\ : backslashes, too', '\\u005C : backslashes, too'), + ('and lots of whitespace: \r\n\t\v\f\b', 'and lots of whitespace: \\u000D\\u000A\\u0009\\u000B\\u000C\\u0008'), + (r'<script>and this</script>', '\\u003Cscript\\u003Eand this\\u003C/script\\u003E'), + ('paragraph separator:\u2029and line separator:\u2028', 'paragraph separator:\\u2029and line separator:\\u2028'), + ) + for value, output in items: + self.check_output(f, value, output) + + def test_clean_html(self): + f = html.clean_html + items = ( + ('<p>I <i>believe</i> in <b>semantic markup</b>!</p>', '<p>I <em>believe</em> in <strong>semantic markup</strong>!</p>'), + ('I escape & I don\'t <a href="#" target="_blank">target</a>', 'I escape & I don\'t <a href="#" >target</a>'), + ('<p>I kill whitespace</p><br clear="all"><p> </p>', '<p>I kill whitespace</p>'), + # also a regression test for #7267: this used to raise an UnicodeDecodeError + ('<p>* foo</p><p>* bar</p>', '<ul>\n<li> foo</li><li> bar</li>\n</ul>'), + ) + for value, output in items: + self.check_output(f, value, output) + + def test_remove_tags(self): + f = html.remove_tags + items = ( + ("<b><i>Yes</i></b>", "b i", "Yes"), + ("<a>x</a> <p><b>y</b></p>", "a b", "x <p>y</p>"), + ) + for value, tags, output in items: + self.assertEqual(f(value, tags), output) diff --git a/tests/utils_tests/test_http.py b/tests/utils_tests/test_http.py new file mode 100644 index 0000000000..6d3bc025af --- /dev/null +++ b/tests/utils_tests/test_http.py @@ -0,0 +1,163 @@ +from datetime import datetime +import sys + +from django.http import HttpResponse, utils +from django.test import RequestFactory +from django.utils.datastructures import MultiValueDict +from django.utils import http +from django.utils import six +from django.utils import unittest + + +class TestUtilsHttp(unittest.TestCase): + + def test_same_origin_true(self): + # Identical + self.assertTrue(http.same_origin('http://foo.com/', 'http://foo.com/')) + # One with trailing slash - see #15617 + self.assertTrue(http.same_origin('http://foo.com', 'http://foo.com/')) + self.assertTrue(http.same_origin('http://foo.com/', 'http://foo.com')) + # With port + self.assertTrue(http.same_origin('https://foo.com:8000', 'https://foo.com:8000/')) + + def test_same_origin_false(self): + # Different scheme + self.assertFalse(http.same_origin('http://foo.com', 'https://foo.com')) + # Different host + self.assertFalse(http.same_origin('http://foo.com', 'http://goo.com')) + # Different host again + self.assertFalse(http.same_origin('http://foo.com', 'http://foo.com.evil.com')) + # Different port + self.assertFalse(http.same_origin('http://foo.com:8000', 'http://foo.com:8001')) + + def test_urlencode(self): + # 2-tuples (the norm) + result = http.urlencode((('a', 1), ('b', 2), ('c', 3))) + self.assertEqual(result, 'a=1&b=2&c=3') + + # A dictionary + result = http.urlencode({ 'a': 1, 'b': 2, 'c': 3}) + acceptable_results = [ + # Need to allow all of these as dictionaries have to be treated as + # unordered + 'a=1&b=2&c=3', + 'a=1&c=3&b=2', + 'b=2&a=1&c=3', + 'b=2&c=3&a=1', + 'c=3&a=1&b=2', + 'c=3&b=2&a=1' + ] + self.assertTrue(result in acceptable_results) + result = http.urlencode({'a': [1, 2]}, doseq=False) + self.assertEqual(result, 'a=%5B%271%27%2C+%272%27%5D') + result = http.urlencode({'a': [1, 2]}, doseq=True) + self.assertEqual(result, 'a=1&a=2') + result = http.urlencode({'a': []}, doseq=True) + self.assertEqual(result, '') + + # A MultiValueDict + result = http.urlencode(MultiValueDict({ + 'name': ['Adrian', 'Simon'], + 'position': ['Developer'] + }), doseq=True) + acceptable_results = [ + # MultiValueDicts are similarly unordered + 'name=Adrian&name=Simon&position=Developer', + 'position=Developer&name=Adrian&name=Simon' + ] + self.assertTrue(result in acceptable_results) + + def test_fix_IE_for_vary(self): + """ + Regression for #16632. + + `fix_IE_for_vary` shouldn't crash when there's no Content-Type header. + """ + + # functions to generate responses + def response_with_unsafe_content_type(): + r = HttpResponse(content_type="text/unsafe") + r['Vary'] = 'Cookie' + return r + + def no_content_response_with_unsafe_content_type(): + # 'Content-Type' always defaulted, so delete it + r = response_with_unsafe_content_type() + del r['Content-Type'] + return r + + # request with & without IE user agent + rf = RequestFactory() + request = rf.get('/') + ie_request = rf.get('/', HTTP_USER_AGENT='MSIE') + + # not IE, unsafe_content_type + response = response_with_unsafe_content_type() + utils.fix_IE_for_vary(request, response) + self.assertTrue('Vary' in response) + + # IE, unsafe_content_type + response = response_with_unsafe_content_type() + utils.fix_IE_for_vary(ie_request, response) + self.assertFalse('Vary' in response) + + # not IE, no_content + response = no_content_response_with_unsafe_content_type() + utils.fix_IE_for_vary(request, response) + self.assertTrue('Vary' in response) + + # IE, no_content + response = no_content_response_with_unsafe_content_type() + utils.fix_IE_for_vary(ie_request, response) + self.assertFalse('Vary' in response) + + def test_base36(self): + # reciprocity works + for n in [0, 1, 1000, 1000000]: + self.assertEqual(n, http.base36_to_int(http.int_to_base36(n))) + if not six.PY3: + self.assertEqual(sys.maxint, http.base36_to_int(http.int_to_base36(sys.maxint))) + + # bad input + self.assertRaises(ValueError, http.int_to_base36, -1) + if not six.PY3: + self.assertRaises(ValueError, http.int_to_base36, sys.maxint + 1) + for n in ['1', 'foo', {1: 2}, (1, 2, 3), 3.141]: + self.assertRaises(TypeError, http.int_to_base36, n) + + for n in ['#', ' ']: + self.assertRaises(ValueError, http.base36_to_int, n) + for n in [123, {1: 2}, (1, 2, 3), 3.141]: + self.assertRaises(TypeError, http.base36_to_int, n) + + # more explicit output testing + for n, b36 in [(0, '0'), (1, '1'), (42, '16'), (818469960, 'django')]: + self.assertEqual(http.int_to_base36(n), b36) + self.assertEqual(http.base36_to_int(b36), n) + + +class ETagProcessingTests(unittest.TestCase): + def testParsing(self): + etags = http.parse_etags(r'"", "etag", "e\"t\"ag", "e\\tag", W/"weak"') + self.assertEqual(etags, ['', 'etag', 'e"t"ag', r'e\tag', 'weak']) + + def testQuoting(self): + quoted_etag = http.quote_etag(r'e\t"ag') + self.assertEqual(quoted_etag, r'"e\\t\"ag"') + + +class HttpDateProcessingTests(unittest.TestCase): + def testParsingRfc1123(self): + parsed = http.parse_http_date('Sun, 06 Nov 1994 08:49:37 GMT') + self.assertEqual(datetime.utcfromtimestamp(parsed), + datetime(1994, 11, 6, 8, 49, 37)) + + def testParsingRfc850(self): + parsed = http.parse_http_date('Sunday, 06-Nov-94 08:49:37 GMT') + self.assertEqual(datetime.utcfromtimestamp(parsed), + datetime(1994, 11, 6, 8, 49, 37)) + + def testParsingAsctime(self): + parsed = http.parse_http_date('Sun Nov 6 08:49:37 1994') + self.assertEqual(datetime.utcfromtimestamp(parsed), + datetime(1994, 11, 6, 8, 49, 37)) diff --git a/tests/utils_tests/test_ipv6.py b/tests/utils_tests/test_ipv6.py new file mode 100644 index 0000000000..1713de82b8 --- /dev/null +++ b/tests/utils_tests/test_ipv6.py @@ -0,0 +1,53 @@ +from __future__ import unicode_literals + +from django.utils import unittest +from django.utils.ipv6 import is_valid_ipv6_address, clean_ipv6_address + +class TestUtilsIPv6(unittest.TestCase): + + def test_validates_correct_plain_address(self): + self.assertTrue(is_valid_ipv6_address('fe80::223:6cff:fe8a:2e8a')) + self.assertTrue(is_valid_ipv6_address('2a02::223:6cff:fe8a:2e8a')) + self.assertTrue(is_valid_ipv6_address('1::2:3:4:5:6:7')) + self.assertTrue(is_valid_ipv6_address('::')) + self.assertTrue(is_valid_ipv6_address('::a')) + self.assertTrue(is_valid_ipv6_address('2::')) + + def test_validates_correct_with_v4mapping(self): + self.assertTrue(is_valid_ipv6_address('::ffff:254.42.16.14')) + self.assertTrue(is_valid_ipv6_address('::ffff:0a0a:0a0a')) + + def test_validates_incorrect_plain_address(self): + self.assertFalse(is_valid_ipv6_address('foo')) + self.assertFalse(is_valid_ipv6_address('127.0.0.1')) + self.assertFalse(is_valid_ipv6_address('12345::')) + self.assertFalse(is_valid_ipv6_address('1::2:3::4')) + self.assertFalse(is_valid_ipv6_address('1::zzz')) + self.assertFalse(is_valid_ipv6_address('1::2:3:4:5:6:7:8')) + self.assertFalse(is_valid_ipv6_address('1:2')) + self.assertFalse(is_valid_ipv6_address('1:::2')) + + def test_validates_incorrect_with_v4mapping(self): + self.assertFalse(is_valid_ipv6_address('::ffff:999.42.16.14')) + self.assertFalse(is_valid_ipv6_address('::ffff:zzzz:0a0a')) + # The ::1.2.3.4 format used to be valid but was deprecated + # in rfc4291 section 2.5.5.1 + self.assertTrue(is_valid_ipv6_address('::254.42.16.14')) + self.assertTrue(is_valid_ipv6_address('::0a0a:0a0a')) + self.assertFalse(is_valid_ipv6_address('::999.42.16.14')) + self.assertFalse(is_valid_ipv6_address('::zzzz:0a0a')) + + def test_cleanes_plain_address(self): + self.assertEqual(clean_ipv6_address('DEAD::0:BEEF'), 'dead::beef') + self.assertEqual(clean_ipv6_address('2001:000:a:0000:0:fe:fe:beef'), '2001:0:a::fe:fe:beef') + self.assertEqual(clean_ipv6_address('2001::a:0000:0:fe:fe:beef'), '2001:0:a::fe:fe:beef') + + def test_cleanes_with_v4_mapping(self): + self.assertEqual(clean_ipv6_address('::ffff:0a0a:0a0a'), '::ffff:10.10.10.10') + self.assertEqual(clean_ipv6_address('::ffff:1234:1234'), '::ffff:18.52.18.52') + self.assertEqual(clean_ipv6_address('::ffff:18.52.18.52'), '::ffff:18.52.18.52') + + def test_unpacks_ipv4(self): + self.assertEqual(clean_ipv6_address('::ffff:0a0a:0a0a', unpack_ipv4=True), '10.10.10.10') + self.assertEqual(clean_ipv6_address('::ffff:1234:1234', unpack_ipv4=True), '18.52.18.52') + self.assertEqual(clean_ipv6_address('::ffff:18.52.18.52', unpack_ipv4=True), '18.52.18.52') diff --git a/tests/utils_tests/test_itercompat.py b/tests/utils_tests/test_itercompat.py new file mode 100644 index 0000000000..02d3463370 --- /dev/null +++ b/tests/utils_tests/test_itercompat.py @@ -0,0 +1,11 @@ +from django.test import TestCase + +from .models import Category, Thing + + +class TestIsIterator(TestCase): + def test_regression(self): + """This failed on Django 1.5/Py2.6 because category has a next method.""" + category = Category.objects.create(name='category') + Thing.objects.create(category=category) + Thing.objects.filter(category=category) diff --git a/tests/utils_tests/test_jslex.py b/tests/utils_tests/test_jslex.py new file mode 100644 index 0000000000..a2af7b4bcf --- /dev/null +++ b/tests/utils_tests/test_jslex.py @@ -0,0 +1,216 @@ +"""Tests for jslex.""" +# encoding: utf-8 +# originally from https://bitbucket.org/ned/jslex + +from django.test import TestCase +from django.utils.jslex import JsLexer, prepare_js_for_gettext + +class JsTokensTest(TestCase): + LEX_CASES = [ + # ids + ("a ABC $ _ a123", ["id a", "id ABC", "id $", "id _", "id a123"]), + (r"\u1234 abc\u0020 \u0065_\u0067", [r"id \u1234", r"id abc\u0020", r"id \u0065_\u0067"]), + # numbers + ("123 1.234 0.123e-3 0 1E+40 1e1 .123", ["dnum 123", "dnum 1.234", "dnum 0.123e-3", "dnum 0", "dnum 1E+40", "dnum 1e1", "dnum .123"]), + ("0x1 0xabCD 0XABcd", ["hnum 0x1", "hnum 0xabCD", "hnum 0XABcd"]), + ("010 0377 090", ["onum 010", "onum 0377", "dnum 0", "dnum 90"]), + ("0xa123ghi", ["hnum 0xa123", "id ghi"]), + # keywords + ("function Function FUNCTION", ["keyword function", "id Function", "id FUNCTION"]), + ("const constructor in inherits", ["keyword const", "id constructor", "keyword in", "id inherits"]), + ("true true_enough", ["reserved true", "id true_enough"]), + # strings + (''' 'hello' "hello" ''', ["string 'hello'", 'string "hello"']), + (r""" 'don\'t' "don\"t" '"' "'" '\'' "\"" """, + [r"""string 'don\'t'""", r'''string "don\"t"''', r"""string '"'""", r'''string "'"''', r"""string '\''""", r'''string "\""''']), + (r'"ƃuıxǝ⅂ ʇdıɹɔsɐʌɐſ\""', [r'string "ƃuıxǝ⅂ ʇdıɹɔsɐʌɐſ\""']), + # comments + ("a//b", ["id a", "linecomment //b"]), + ("/****/a/=2//hello", ["comment /****/", "id a", "punct /=", "dnum 2", "linecomment //hello"]), + ("/*\n * Header\n */\na=1;", ["comment /*\n * Header\n */", "id a", "punct =", "dnum 1", "punct ;"]), + # punctuation + ("a+++b", ["id a", "punct ++", "punct +", "id b"]), + # regex + (r"a=/a*/,1", ["id a", "punct =", "regex /a*/", "punct ,", "dnum 1"]), + (r"a=/a*[^/]+/,1", ["id a", "punct =", "regex /a*[^/]+/", "punct ,", "dnum 1"]), + (r"a=/a*\[^/,1", ["id a", "punct =", r"regex /a*\[^/", "punct ,", "dnum 1"]), + (r"a=/\//,1", ["id a", "punct =", r"regex /\//", "punct ,", "dnum 1"]), + + # next two are from http://www.mozilla.org/js/language/js20-2002-04/rationale/syntax.html#regular-expressions + ("""for (var x = a in foo && "</x>" || mot ? z:/x:3;x<5;y</g/i) {xyz(x++);}""", + ["keyword for", "punct (", "keyword var", "id x", "punct =", "id a", "keyword in", + "id foo", "punct &&", 'string "</x>"', "punct ||", "id mot", "punct ?", "id z", + "punct :", "regex /x:3;x<5;y</g", "punct /", "id i", "punct )", "punct {", + "id xyz", "punct (", "id x", "punct ++", "punct )", "punct ;", "punct }"]), + ("""for (var x = a in foo && "</x>" || mot ? z/x:3;x<5;y</g/i) {xyz(x++);}""", + ["keyword for", "punct (", "keyword var", "id x", "punct =", "id a", "keyword in", + "id foo", "punct &&", 'string "</x>"', "punct ||", "id mot", "punct ?", "id z", + "punct /", "id x", "punct :", "dnum 3", "punct ;", "id x", "punct <", "dnum 5", + "punct ;", "id y", "punct <", "regex /g/i", "punct )", "punct {", + "id xyz", "punct (", "id x", "punct ++", "punct )", "punct ;", "punct }"]), + + # Various "illegal" regexes that are valid according to the std. + (r"""/????/, /++++/, /[----]/ """, ["regex /????/", "punct ,", "regex /++++/", "punct ,", "regex /[----]/"]), + + # Stress cases from http://stackoverflow.com/questions/5533925/what-javascript-constructs-does-jslex-incorrectly-lex/5573409#5573409 + (r"""/\[/""", [r"""regex /\[/"""]), + (r"""/[i]/""", [r"""regex /[i]/"""]), + (r"""/[\]]/""", [r"""regex /[\]]/"""]), + (r"""/a[\]]/""", [r"""regex /a[\]]/"""]), + (r"""/a[\]]b/""", [r"""regex /a[\]]b/"""]), + (r"""/[\]/]/gi""", [r"""regex /[\]/]/gi"""]), + (r"""/\[[^\]]+\]/gi""", [r"""regex /\[[^\]]+\]/gi"""]), + (""" + rexl.re = { + NAME: /^(?!\d)(?:\w)+|^"(?:[^"]|"")+"/, + UNQUOTED_LITERAL: /^@(?:(?!\d)(?:\w|\:)+|^"(?:[^"]|"")+")\[[^\]]+\]/, + QUOTED_LITERAL: /^'(?:[^']|'')*'/, + NUMERIC_LITERAL: /^[0-9]+(?:\.[0-9]*(?:[eE][-+][0-9]+)?)?/, + SYMBOL: /^(?:==|=|<>|<=|<|>=|>|!~~|!~|~~|~|!==|!=|!~=|!~|!|&|\||\.|\:|,|\(|\)|\[|\]|\{|\}|\?|\:|;|@|\^|\/\+|\/|\*|\+|-)/ + }; + """, + ["id rexl", "punct .", "id re", "punct =", "punct {", + "id NAME", "punct :", r"""regex /^(?!\d)(?:\w)+|^"(?:[^"]|"")+"/""", "punct ,", + "id UNQUOTED_LITERAL", "punct :", r"""regex /^@(?:(?!\d)(?:\w|\:)+|^"(?:[^"]|"")+")\[[^\]]+\]/""", "punct ,", + "id QUOTED_LITERAL", "punct :", r"""regex /^'(?:[^']|'')*'/""", "punct ,", + "id NUMERIC_LITERAL", "punct :", r"""regex /^[0-9]+(?:\.[0-9]*(?:[eE][-+][0-9]+)?)?/""", "punct ,", + "id SYMBOL", "punct :", r"""regex /^(?:==|=|<>|<=|<|>=|>|!~~|!~|~~|~|!==|!=|!~=|!~|!|&|\||\.|\:|,|\(|\)|\[|\]|\{|\}|\?|\:|;|@|\^|\/\+|\/|\*|\+|-)/""", + "punct }", "punct ;" + ]), + + (""" + rexl.re = { + NAME: /^(?!\d)(?:\w)+|^"(?:[^"]|"")+"/, + UNQUOTED_LITERAL: /^@(?:(?!\d)(?:\w|\:)+|^"(?:[^"]|"")+")\[[^\]]+\]/, + QUOTED_LITERAL: /^'(?:[^']|'')*'/, + NUMERIC_LITERAL: /^[0-9]+(?:\.[0-9]*(?:[eE][-+][0-9]+)?)?/, + SYMBOL: /^(?:==|=|<>|<=|<|>=|>|!~~|!~|~~|~|!==|!=|!~=|!~|!|&|\||\.|\:|,|\(|\)|\[|\]|\{|\}|\?|\:|;|@|\^|\/\+|\/|\*|\+|-)/ + }; + str = '"'; + """, + ["id rexl", "punct .", "id re", "punct =", "punct {", + "id NAME", "punct :", r"""regex /^(?!\d)(?:\w)+|^"(?:[^"]|"")+"/""", "punct ,", + "id UNQUOTED_LITERAL", "punct :", r"""regex /^@(?:(?!\d)(?:\w|\:)+|^"(?:[^"]|"")+")\[[^\]]+\]/""", "punct ,", + "id QUOTED_LITERAL", "punct :", r"""regex /^'(?:[^']|'')*'/""", "punct ,", + "id NUMERIC_LITERAL", "punct :", r"""regex /^[0-9]+(?:\.[0-9]*(?:[eE][-+][0-9]+)?)?/""", "punct ,", + "id SYMBOL", "punct :", r"""regex /^(?:==|=|<>|<=|<|>=|>|!~~|!~|~~|~|!==|!=|!~=|!~|!|&|\||\.|\:|,|\(|\)|\[|\]|\{|\}|\?|\:|;|@|\^|\/\+|\/|\*|\+|-)/""", + "punct }", "punct ;", + "id str", "punct =", """string '"'""", "punct ;", + ]), + + (r""" this._js = "e.str(\"" + this.value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + "\")"; """, + ["keyword this", "punct .", "id _js", "punct =", r'''string "e.str(\""''', "punct +", "keyword this", "punct .", + "id value", "punct .", "id replace", "punct (", r"regex /\\/g", "punct ,", r'string "\\\\"', "punct )", + "punct .", "id replace", "punct (", r'regex /"/g', "punct ,", r'string "\\\""', "punct )", "punct +", + r'string "\")"', "punct ;"]), + ] + +def make_function(input, toks): + def test_func(self): + lexer = JsLexer() + result = ["%s %s" % (name, tok) for name, tok in lexer.lex(input) if name != 'ws'] + self.assertListEqual(result, toks) + return test_func + +for i, (input, toks) in enumerate(JsTokensTest.LEX_CASES): + setattr(JsTokensTest, "test_case_%d" % i, make_function(input, toks)) + + +GETTEXT_CASES = ( + ( + r""" + a = 1; /* /[0-9]+/ */ + b = 0x2a0b / 1; // /[0-9]+/ + c = 3; + """, + r""" + a = 1; /* /[0-9]+/ */ + b = 0x2a0b / 1; // /[0-9]+/ + c = 3; + """ + ), ( + r""" + a = 1.234e-5; + /* + * /[0-9+/ + */ + b = .0123; + """, + r""" + a = 1.234e-5; + /* + * /[0-9+/ + */ + b = .0123; + """ + ), ( + r""" + x = y / z; + alert(gettext("hello")); + x /= 3; + """, + r""" + x = y / z; + alert(gettext("hello")); + x /= 3; + """ + ), ( + r""" + s = "Hello \"th/foo/ere\""; + s = 'He\x23llo \'th/foo/ere\''; + s = 'slash quote \", just quote "'; + """, + r""" + s = "Hello \"th/foo/ere\""; + s = "He\x23llo \'th/foo/ere\'"; + s = "slash quote \", just quote \""; + """ + ), ( + r""" + s = "Line continuation\ + continued /hello/ still the string";/hello/; + """, + r""" + s = "Line continuation\ + continued /hello/ still the string";"REGEX"; + """ + ), ( + r""" + var regex = /pattern/; + var regex2 = /matter/gm; + var regex3 = /[*/]+/gm.foo("hey"); + """, + r""" + var regex = "REGEX"; + var regex2 = "REGEX"; + var regex3 = "REGEX".foo("hey"); + """ + ), ( + r""" + for (var x = a in foo && "</x>" || mot ? z:/x:3;x<5;y</g/i) {xyz(x++);} + for (var x = a in foo && "</x>" || mot ? z/x:3;x<5;y</g/i) {xyz(x++);} + """, + r""" + for (var x = a in foo && "</x>" || mot ? z:"REGEX"/i) {xyz(x++);} + for (var x = a in foo && "</x>" || mot ? z/x:3;x<5;y<"REGEX") {xyz(x++);} + """ + ), ( + r""" + \u1234xyz = gettext('Hello there'); + """, r""" + Uu1234xyz = gettext("Hello there"); + """ + ) +) + + +class JsToCForGettextTest(TestCase): + pass + +def make_function(js, c): + def test_func(self): + self.assertMultiLineEqual(prepare_js_for_gettext(js), c) + return test_func + +for i, pair in enumerate(GETTEXT_CASES): + setattr(JsToCForGettextTest, "test_case_%d" % i, make_function(*pair)) diff --git a/tests/utils_tests/test_module/__init__.py b/tests/utils_tests/test_module/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/utils_tests/test_module/__init__.py diff --git a/tests/utils_tests/test_module/bad_module.py b/tests/utils_tests/test_module/bad_module.py new file mode 100644 index 0000000000..cc0cd168ce --- /dev/null +++ b/tests/utils_tests/test_module/bad_module.py @@ -0,0 +1,3 @@ +import a_package_name_that_does_not_exist + +content = 'Bad Module'
\ No newline at end of file diff --git a/tests/utils_tests/test_module/good_module.py b/tests/utils_tests/test_module/good_module.py new file mode 100644 index 0000000000..0ca689832a --- /dev/null +++ b/tests/utils_tests/test_module/good_module.py @@ -0,0 +1 @@ +content = 'Good Module'
\ No newline at end of file diff --git a/tests/utils_tests/test_module_loading.py b/tests/utils_tests/test_module_loading.py new file mode 100644 index 0000000000..0a905ef2f1 --- /dev/null +++ b/tests/utils_tests/test_module_loading.py @@ -0,0 +1,194 @@ +import os +import sys +import imp +from zipimport import zipimporter + +from django.core.exceptions import ImproperlyConfigured +from django.utils import unittest +from django.utils.importlib import import_module +from django.utils.module_loading import import_by_path, module_has_submodule +from django.utils._os import upath + + +class DefaultLoader(unittest.TestCase): + def setUp(self): + sys.meta_path.insert(0, ProxyFinder()) + + def tearDown(self): + sys.meta_path.pop(0) + + def test_loader(self): + "Normal module existence can be tested" + test_module = import_module('utils_tests.test_module') + test_no_submodule = import_module( + 'utils_tests.test_no_submodule') + + # An importable child + self.assertTrue(module_has_submodule(test_module, 'good_module')) + mod = import_module('utils_tests.test_module.good_module') + self.assertEqual(mod.content, 'Good Module') + + # A child that exists, but will generate an import error if loaded + self.assertTrue(module_has_submodule(test_module, 'bad_module')) + self.assertRaises(ImportError, import_module, 'utils_tests.test_module.bad_module') + + # A child that doesn't exist + self.assertFalse(module_has_submodule(test_module, 'no_such_module')) + self.assertRaises(ImportError, import_module, 'utils_tests.test_module.no_such_module') + + # A child that doesn't exist, but is the name of a package on the path + self.assertFalse(module_has_submodule(test_module, 'django')) + self.assertRaises(ImportError, import_module, 'utils_tests.test_module.django') + + # Don't be confused by caching of import misses + import types # causes attempted import of utils_tests.types + self.assertFalse(module_has_submodule(sys.modules['utils_tests'], 'types')) + + # A module which doesn't have a __path__ (so no submodules) + self.assertFalse(module_has_submodule(test_no_submodule, 'anything')) + self.assertRaises(ImportError, import_module, + 'utils_tests.test_no_submodule.anything') + +class EggLoader(unittest.TestCase): + def setUp(self): + self.old_path = sys.path[:] + self.egg_dir = '%s/eggs' % os.path.dirname(upath(__file__)) + + def tearDown(self): + sys.path = self.old_path + sys.path_importer_cache.clear() + + sys.modules.pop('egg_module.sub1.sub2.bad_module', None) + sys.modules.pop('egg_module.sub1.sub2.good_module', None) + sys.modules.pop('egg_module.sub1.sub2', None) + sys.modules.pop('egg_module.sub1', None) + sys.modules.pop('egg_module.bad_module', None) + sys.modules.pop('egg_module.good_module', None) + sys.modules.pop('egg_module', None) + + def test_shallow_loader(self): + "Module existence can be tested inside eggs" + egg_name = '%s/test_egg.egg' % self.egg_dir + sys.path.append(egg_name) + egg_module = import_module('egg_module') + + # An importable child + self.assertTrue(module_has_submodule(egg_module, 'good_module')) + mod = import_module('egg_module.good_module') + self.assertEqual(mod.content, 'Good Module') + + # A child that exists, but will generate an import error if loaded + self.assertTrue(module_has_submodule(egg_module, 'bad_module')) + self.assertRaises(ImportError, import_module, 'egg_module.bad_module') + + # A child that doesn't exist + self.assertFalse(module_has_submodule(egg_module, 'no_such_module')) + self.assertRaises(ImportError, import_module, 'egg_module.no_such_module') + + def test_deep_loader(self): + "Modules deep inside an egg can still be tested for existence" + egg_name = '%s/test_egg.egg' % self.egg_dir + sys.path.append(egg_name) + egg_module = import_module('egg_module.sub1.sub2') + + # An importable child + self.assertTrue(module_has_submodule(egg_module, 'good_module')) + mod = import_module('egg_module.sub1.sub2.good_module') + self.assertEqual(mod.content, 'Deep Good Module') + + # A child that exists, but will generate an import error if loaded + self.assertTrue(module_has_submodule(egg_module, 'bad_module')) + self.assertRaises(ImportError, import_module, 'egg_module.sub1.sub2.bad_module') + + # A child that doesn't exist + self.assertFalse(module_has_submodule(egg_module, 'no_such_module')) + self.assertRaises(ImportError, import_module, 'egg_module.sub1.sub2.no_such_module') + + +class ModuleImportTestCase(unittest.TestCase): + def test_import_by_path(self): + cls = import_by_path( + 'django.utils.module_loading.import_by_path') + self.assertEqual(cls, import_by_path) + + # Test exceptions raised + for path in ('no_dots_in_path', 'unexistent.path', + 'utils_tests.unexistent'): + self.assertRaises(ImproperlyConfigured, import_by_path, path) + + with self.assertRaises(ImproperlyConfigured) as cm: + import_by_path('unexistent.module.path', error_prefix="Foo") + self.assertTrue(str(cm.exception).startswith('Foo')) + + def test_import_error_traceback(self): + """Test preserving the original traceback on an ImportError.""" + try: + import_by_path('test_module.bad_module.content') + except ImproperlyConfigured: + traceback = sys.exc_info()[2] + + self.assertIsNotNone(traceback.tb_next.tb_next, + 'Should have more than the calling frame in the traceback.') + + +class ProxyFinder(object): + def __init__(self): + self._cache = {} + + def find_module(self, fullname, path=None): + tail = fullname.rsplit('.', 1)[-1] + try: + fd, fn, info = imp.find_module(tail, path) + if fullname in self._cache: + old_fd = self._cache[fullname][0] + if old_fd: + old_fd.close() + self._cache[fullname] = (fd, fn, info) + except ImportError: + return None + else: + return self # this is a loader as well + + def load_module(self, fullname): + if fullname in sys.modules: + return sys.modules[fullname] + fd, fn, info = self._cache[fullname] + try: + return imp.load_module(fullname, fd, fn, info) + finally: + if fd: + fd.close() + +class TestFinder(object): + def __init__(self, *args, **kwargs): + self.importer = zipimporter(*args, **kwargs) + + def find_module(self, path): + importer = self.importer.find_module(path) + if importer is None: + return + return TestLoader(importer) + +class TestLoader(object): + def __init__(self, importer): + self.importer = importer + + def load_module(self, name): + mod = self.importer.load_module(name) + mod.__loader__ = self + return mod + +class CustomLoader(EggLoader): + """The Custom Loader test is exactly the same as the EggLoader, but + it uses a custom defined Loader and Finder that is intentionally + split into two classes. Although the EggLoader combines both functions + into one class, this isn't required. + """ + def setUp(self): + super(CustomLoader, self).setUp() + sys.path_hooks.insert(0, TestFinder) + sys.path_importer_cache.clear() + + def tearDown(self): + super(CustomLoader, self).tearDown() + sys.path_hooks.pop(0) diff --git a/tests/utils_tests/test_no_submodule.py b/tests/utils_tests/test_no_submodule.py new file mode 100644 index 0000000000..3c424ac788 --- /dev/null +++ b/tests/utils_tests/test_no_submodule.py @@ -0,0 +1 @@ +# Used to test for modules which don't have submodules. diff --git a/tests/utils_tests/test_numberformat.py b/tests/utils_tests/test_numberformat.py new file mode 100644 index 0000000000..f9d9031e48 --- /dev/null +++ b/tests/utils_tests/test_numberformat.py @@ -0,0 +1,47 @@ +from unittest import TestCase +from django.utils.numberformat import format as nformat +from sys import float_info + + +class TestNumberFormat(TestCase): + + def test_format_number(self): + self.assertEqual(nformat(1234, '.'), '1234') + self.assertEqual(nformat(1234.2, '.'), '1234.2') + self.assertEqual(nformat(1234, '.', decimal_pos=2), '1234.00') + self.assertEqual(nformat(1234, '.', grouping=2, thousand_sep=','), + '1234') + self.assertEqual(nformat(1234, '.', grouping=2, thousand_sep=',', + force_grouping=True), '12,34') + self.assertEqual(nformat(-1234.33, '.', decimal_pos=1), '-1234.3') + + def test_format_string(self): + self.assertEqual(nformat('1234', '.'), '1234') + self.assertEqual(nformat('1234.2', '.'), '1234.2') + self.assertEqual(nformat('1234', '.', decimal_pos=2), '1234.00') + self.assertEqual(nformat('1234', '.', grouping=2, thousand_sep=','), + '1234') + self.assertEqual(nformat('1234', '.', grouping=2, thousand_sep=',', + force_grouping=True), '12,34') + self.assertEqual(nformat('-1234.33', '.', decimal_pos=1), '-1234.3') + + def test_large_number(self): + most_max = ('{0}179769313486231570814527423731704356798070567525844996' + '598917476803157260780028538760589558632766878171540458953' + '514382464234321326889464182768467546703537516986049910576' + '551282076245490090389328944075868508455133942304583236903' + '222948165808559332123348274797826204144723168738177180919' + '29988125040402618412485836{1}') + most_max2 = ('{0}35953862697246314162905484746340871359614113505168999' + '31978349536063145215600570775211791172655337563430809179' + '07028764928468642653778928365536935093407075033972099821' + '15310256415249098018077865788815173701691026788460916647' + '38064458963316171186642466965495956524082894463374763543' + '61838599762500808052368249716736') + int_max = int(float_info.max) + self.assertEqual(nformat(int_max, '.'), most_max.format('', '8')) + self.assertEqual(nformat(int_max + 1, '.'), most_max.format('', '9')) + self.assertEqual(nformat(int_max * 2, '.'), most_max2.format('')) + self.assertEqual(nformat(0 - int_max, '.'), most_max.format('-', '8')) + self.assertEqual(nformat(-1 - int_max, '.'), most_max.format('-', '9')) + self.assertEqual(nformat(-2 * int_max, '.'), most_max2.format('-')) diff --git a/tests/utils_tests/test_os_utils.py b/tests/utils_tests/test_os_utils.py new file mode 100644 index 0000000000..a205d67431 --- /dev/null +++ b/tests/utils_tests/test_os_utils.py @@ -0,0 +1,26 @@ +import os + +from django.utils import unittest +from django.utils._os import safe_join + + +class SafeJoinTests(unittest.TestCase): + def test_base_path_ends_with_sep(self): + drive, path = os.path.splitdrive(safe_join("/abc/", "abc")) + self.assertEqual( + path, + "{0}abc{0}abc".format(os.path.sep) + ) + + def test_root_path(self): + drive, path = os.path.splitdrive(safe_join("/", "path")) + self.assertEqual( + path, + "{0}path".format(os.path.sep), + ) + + drive, path = os.path.splitdrive(safe_join("/", "")) + self.assertEqual( + path, + os.path.sep, + ) diff --git a/tests/utils_tests/test_regex_helper.py b/tests/utils_tests/test_regex_helper.py new file mode 100644 index 0000000000..41f4a4f85d --- /dev/null +++ b/tests/utils_tests/test_regex_helper.py @@ -0,0 +1,50 @@ +from __future__ import unicode_literals + +from django.utils import regex_helper +from django.utils import unittest + + +class NormalizeTests(unittest.TestCase): + def test_empty(self): + pattern = r"" + expected = [('', [])] + result = regex_helper.normalize(pattern) + self.assertEqual(result, expected) + + def test_escape(self): + pattern = r"\\\^\$\.\|\?\*\+\(\)\[" + expected = [('\\^$.|?*+()[', [])] + result = regex_helper.normalize(pattern) + self.assertEqual(result, expected) + + def test_group_positional(self): + pattern = r"(.*)-(.+)" + expected = [('%(_0)s-%(_1)s', ['_0', '_1'])] + result = regex_helper.normalize(pattern) + self.assertEqual(result, expected) + + def test_group_ignored(self): + pattern = r"(?i)(?L)(?m)(?s)(?u)(?#)" + expected = [('', [])] + result = regex_helper.normalize(pattern) + self.assertEqual(result, expected) + + def test_group_noncapturing(self): + pattern = r"(?:non-capturing)" + expected = [('non-capturing', [])] + result = regex_helper.normalize(pattern) + self.assertEqual(result, expected) + + def test_group_named(self): + pattern = r"(?P<first_group_name>.*)-(?P<second_group_name>.*)" + expected = [('%(first_group_name)s-%(second_group_name)s', + ['first_group_name', 'second_group_name'])] + result = regex_helper.normalize(pattern) + self.assertEqual(result, expected) + + def test_group_backreference(self): + pattern = r"(?P<first_group_name>.*)-(?P=first_group_name)" + expected = [('%(first_group_name)s-%(first_group_name)s', + ['first_group_name'])] + result = regex_helper.normalize(pattern) + self.assertEqual(result, expected) diff --git a/tests/utils_tests/test_simplelazyobject.py b/tests/utils_tests/test_simplelazyobject.py new file mode 100644 index 0000000000..883e60aa81 --- /dev/null +++ b/tests/utils_tests/test_simplelazyobject.py @@ -0,0 +1,154 @@ +from __future__ import unicode_literals + +import copy +import pickle +import sys + +from django.utils import six +from django.utils.unittest import TestCase +from django.utils.functional import SimpleLazyObject, empty + + +class _ComplexObject(object): + def __init__(self, name): + self.name = name + + def __eq__(self, other): + return self.name == other.name + + def __hash__(self): + return hash(self.name) + + if six.PY3: + def __bytes__(self): + return ("I am _ComplexObject(%r)" % self.name).encode("utf-8") + + def __str__(self): + return self.name + + else: + def __str__(self): + return b"I am _ComplexObject(%r)" % str(self.name) + + def __unicode__(self): + return self.name + + def __repr__(self): + return "_ComplexObject(%r)" % self.name + + +complex_object = lambda: _ComplexObject("joe") + + +class TestUtilsSimpleLazyObject(TestCase): + """ + Tests for SimpleLazyObject + """ + # Note that concrete use cases for SimpleLazyObject are also found in the + # auth context processor tests (unless the implementation of that function + # is changed). + + def test_equality(self): + self.assertEqual(complex_object(), SimpleLazyObject(complex_object)) + self.assertEqual(SimpleLazyObject(complex_object), complex_object()) + + def test_hash(self): + # hash() equality would not be true for many objects, but it should be + # for _ComplexObject + self.assertEqual(hash(complex_object()), + hash(SimpleLazyObject(complex_object))) + + def test_repr(self): + # First, for an unevaluated SimpleLazyObject + x = SimpleLazyObject(complex_object) + # __repr__ contains __repr__ of setup function and does not evaluate + # the SimpleLazyObject + self.assertEqual("<SimpleLazyObject: %r>" % complex_object, repr(x)) + self.assertEqual(empty, x._wrapped) + + # Second, for an evaluated SimpleLazyObject + name = x.name # evaluate + self.assertTrue(isinstance(x._wrapped, _ComplexObject)) + # __repr__ contains __repr__ of wrapped object + self.assertEqual("<SimpleLazyObject: %r>" % x._wrapped, repr(x)) + + def test_bytes(self): + self.assertEqual(b"I am _ComplexObject('joe')", + bytes(SimpleLazyObject(complex_object))) + + def test_text(self): + self.assertEqual("joe", six.text_type(SimpleLazyObject(complex_object))) + + def test_class(self): + # This is important for classes that use __class__ in things like + # equality tests. + self.assertEqual(_ComplexObject, SimpleLazyObject(complex_object).__class__) + + def test_deepcopy(self): + # Check that we *can* do deep copy, and that it returns the right + # objects. + + # First, for an unevaluated SimpleLazyObject + s = SimpleLazyObject(complex_object) + self.assertIs(s._wrapped, empty) + s2 = copy.deepcopy(s) + # something has gone wrong is s is evaluated + self.assertIs(s._wrapped, empty) + self.assertEqual(s2, complex_object()) + + # Second, for an evaluated SimpleLazyObject + name = s.name # evaluate + self.assertIsNot(s._wrapped, empty) + s3 = copy.deepcopy(s) + self.assertEqual(s3, complex_object()) + + def test_none(self): + i = [0] + + def f(): + i[0] += 1 + return None + + x = SimpleLazyObject(f) + self.assertEqual(str(x), "None") + self.assertEqual(i, [1]) + self.assertEqual(str(x), "None") + self.assertEqual(i, [1]) + + def test_bool(self): + x = SimpleLazyObject(lambda: 3) + self.assertTrue(x) + x = SimpleLazyObject(lambda: 0) + self.assertFalse(x) + + def test_pickle_complex(self): + # See ticket #16563 + x = SimpleLazyObject(complex_object) + pickled = pickle.dumps(x) + unpickled = pickle.loads(pickled) + self.assertEqual(unpickled, x) + self.assertEqual(six.text_type(unpickled), six.text_type(x)) + self.assertEqual(unpickled.name, x.name) + + def test_dict(self): + # See ticket #18447 + lazydict = SimpleLazyObject(lambda: {'one': 1}) + self.assertEqual(lazydict['one'], 1) + lazydict['one'] = -1 + self.assertEqual(lazydict['one'], -1) + del lazydict['one'] + with self.assertRaises(KeyError): + lazydict['one'] + + def test_trace(self): + # See ticket #19456 + old_trace_func = sys.gettrace() + try: + def trace_func(frame, event, arg): + frame.f_locals['self'].__class__ + if old_trace_func is not None: + old_trace_func(frame, event, arg) + sys.settrace(trace_func) + SimpleLazyObject(None) + finally: + sys.settrace(old_trace_func) diff --git a/tests/utils_tests/test_termcolors.py b/tests/utils_tests/test_termcolors.py new file mode 100644 index 0000000000..8cc28009cd --- /dev/null +++ b/tests/utils_tests/test_termcolors.py @@ -0,0 +1,157 @@ +from django.utils import unittest +from django.utils.termcolors import (parse_color_setting, PALETTES, + DEFAULT_PALETTE, LIGHT_PALETTE, DARK_PALETTE, NOCOLOR_PALETTE, colorize) + + +class TermColorTests(unittest.TestCase): + + def test_empty_string(self): + self.assertEqual(parse_color_setting(''), PALETTES[DEFAULT_PALETTE]) + + def test_simple_palette(self): + self.assertEqual(parse_color_setting('light'), PALETTES[LIGHT_PALETTE]) + self.assertEqual(parse_color_setting('dark'), PALETTES[DARK_PALETTE]) + self.assertEqual(parse_color_setting('nocolor'), None) + + def test_fg(self): + self.assertEqual(parse_color_setting('error=green'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'})) + + def test_fg_bg(self): + self.assertEqual(parse_color_setting('error=green/blue'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'bg':'blue'})) + + def test_fg_opts(self): + self.assertEqual(parse_color_setting('error=green,blink'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'opts': ('blink',)})) + self.assertEqual(parse_color_setting('error=green,bold,blink'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'opts': ('blink','bold')})) + + def test_fg_bg_opts(self): + self.assertEqual(parse_color_setting('error=green/blue,blink'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'bg':'blue', 'opts': ('blink',)})) + self.assertEqual(parse_color_setting('error=green/blue,bold,blink'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'bg':'blue', 'opts': ('blink','bold')})) + + def test_override_palette(self): + self.assertEqual(parse_color_setting('light;error=green'), + dict(PALETTES[LIGHT_PALETTE], + ERROR={'fg':'green'})) + + def test_override_nocolor(self): + self.assertEqual(parse_color_setting('nocolor;error=green'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg': 'green'})) + + def test_reverse_override(self): + self.assertEqual(parse_color_setting('error=green;light'), PALETTES[LIGHT_PALETTE]) + + def test_multiple_roles(self): + self.assertEqual(parse_color_setting('error=green;sql_field=blue'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'}, + SQL_FIELD={'fg':'blue'})) + + def test_override_with_multiple_roles(self): + self.assertEqual(parse_color_setting('light;error=green;sql_field=blue'), + dict(PALETTES[LIGHT_PALETTE], + ERROR={'fg':'green'}, + SQL_FIELD={'fg':'blue'})) + + def test_empty_definition(self): + self.assertEqual(parse_color_setting(';'), None) + self.assertEqual(parse_color_setting('light;'), PALETTES[LIGHT_PALETTE]) + self.assertEqual(parse_color_setting(';;;'), None) + + def test_empty_options(self): + self.assertEqual(parse_color_setting('error=green,'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'})) + self.assertEqual(parse_color_setting('error=green,,,'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'})) + self.assertEqual(parse_color_setting('error=green,,blink,,'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'opts': ('blink',)})) + + def test_bad_palette(self): + self.assertEqual(parse_color_setting('unknown'), None) + + def test_bad_role(self): + self.assertEqual(parse_color_setting('unknown='), None) + self.assertEqual(parse_color_setting('unknown=green'), None) + self.assertEqual(parse_color_setting('unknown=green;sql_field=blue'), + dict(PALETTES[NOCOLOR_PALETTE], + SQL_FIELD={'fg':'blue'})) + + def test_bad_color(self): + self.assertEqual(parse_color_setting('error='), None) + self.assertEqual(parse_color_setting('error=;sql_field=blue'), + dict(PALETTES[NOCOLOR_PALETTE], + SQL_FIELD={'fg':'blue'})) + self.assertEqual(parse_color_setting('error=unknown'), None) + self.assertEqual(parse_color_setting('error=unknown;sql_field=blue'), + dict(PALETTES[NOCOLOR_PALETTE], + SQL_FIELD={'fg':'blue'})) + self.assertEqual(parse_color_setting('error=green/unknown'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'})) + self.assertEqual(parse_color_setting('error=green/blue/something'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'bg': 'blue'})) + self.assertEqual(parse_color_setting('error=green/blue/something,blink'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'bg': 'blue', 'opts': ('blink',)})) + + def test_bad_option(self): + self.assertEqual(parse_color_setting('error=green,unknown'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'})) + self.assertEqual(parse_color_setting('error=green,unknown,blink'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'opts': ('blink',)})) + + def test_role_case(self): + self.assertEqual(parse_color_setting('ERROR=green'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'})) + self.assertEqual(parse_color_setting('eRrOr=green'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'})) + + def test_color_case(self): + self.assertEqual(parse_color_setting('error=GREEN'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'})) + self.assertEqual(parse_color_setting('error=GREEN/BLUE'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'bg':'blue'})) + + self.assertEqual(parse_color_setting('error=gReEn'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'})) + self.assertEqual(parse_color_setting('error=gReEn/bLuE'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'bg':'blue'})) + + def test_opts_case(self): + self.assertEqual(parse_color_setting('error=green,BLINK'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'opts': ('blink',)})) + + self.assertEqual(parse_color_setting('error=green,bLiNk'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'opts': ('blink',)})) + + def test_colorize_empty_text(self): + self.assertEqual(colorize(text=None), '\x1b[m\x1b[0m') + self.assertEqual(colorize(text=''), '\x1b[m\x1b[0m') + + self.assertEqual(colorize(text=None, opts=('noreset')), '\x1b[m') + self.assertEqual(colorize(text='', opts=('noreset')), '\x1b[m') diff --git a/tests/utils_tests/test_text.py b/tests/utils_tests/test_text.py new file mode 100644 index 0000000000..c9dde6b1b3 --- /dev/null +++ b/tests/utils_tests/test_text.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import warnings + +from django.test import SimpleTestCase +from django.utils import text + +class TestUtilsText(SimpleTestCase): + + def test_truncate_chars(self): + truncator = text.Truncator( + 'The quick brown fox jumped over the lazy dog.' + ) + self.assertEqual('The quick brown fox jumped over the lazy dog.', + truncator.chars(100)), + self.assertEqual('The quick brown fox ...', + truncator.chars(23)), + self.assertEqual('The quick brown fo.....', + truncator.chars(23, '.....')), + + # Ensure that we normalize our unicode data first + nfc = text.Truncator('o\xfco\xfco\xfco\xfc') + nfd = text.Truncator('ou\u0308ou\u0308ou\u0308ou\u0308') + self.assertEqual('oüoüoüoü', nfc.chars(8)) + self.assertEqual('oüoüoüoü', nfd.chars(8)) + self.assertEqual('oü...', nfc.chars(5)) + self.assertEqual('oü...', nfd.chars(5)) + + # Ensure the final length is calculated correctly when there are + # combining characters with no precomposed form, and that combining + # characters are not split up. + truncator = text.Truncator('-B\u030AB\u030A----8') + self.assertEqual('-B\u030A...', truncator.chars(5)) + self.assertEqual('-B\u030AB\u030A-...', truncator.chars(7)) + self.assertEqual('-B\u030AB\u030A----8', truncator.chars(8)) + + # Ensure the length of the end text is correctly calculated when it + # contains combining characters with no precomposed form. + truncator = text.Truncator('-----') + self.assertEqual('---B\u030A', truncator.chars(4, 'B\u030A')) + self.assertEqual('-----', truncator.chars(5, 'B\u030A')) + + # Make a best effort to shorten to the desired length, but requesting + # a length shorter than the ellipsis shouldn't break + self.assertEqual('...', text.Truncator('asdf').chars(1)) + + def test_truncate_words(self): + truncator = text.Truncator('The quick brown fox jumped over the lazy ' + 'dog.') + self.assertEqual('The quick brown fox jumped over the lazy dog.', + truncator.words(10)) + self.assertEqual('The quick brown fox...', truncator.words(4)) + self.assertEqual('The quick brown fox[snip]', + truncator.words(4, '[snip]')) + + def test_truncate_html_words(self): + truncator = text.Truncator('<p id="par"><strong><em>The quick brown fox' + ' jumped over the lazy dog.</em></strong></p>') + self.assertEqual('<p id="par"><strong><em>The quick brown fox jumped over' + ' the lazy dog.</em></strong></p>', truncator.words(10, html=True)) + self.assertEqual('<p id="par"><strong><em>The quick brown fox...</em>' + '</strong></p>', truncator.words(4, html=True)) + self.assertEqual('<p id="par"><strong><em>The quick brown fox....</em>' + '</strong></p>', truncator.words(4, '....', html=True)) + self.assertEqual('<p id="par"><strong><em>The quick brown fox</em>' + '</strong></p>', truncator.words(4, '', html=True)) + + # Test with new line inside tag + truncator = text.Truncator('<p>The quick <a href="xyz.html"\n' + 'id="mylink">brown fox</a> jumped over the lazy dog.</p>') + self.assertEqual('<p>The quick <a href="xyz.html"\n' + 'id="mylink">brown...</a></p>', truncator.words(3, '...', html=True)) + + # Test self-closing tags + truncator = text.Truncator('<br/>The <hr />quick brown fox jumped over' + ' the lazy dog.') + self.assertEqual('<br/>The <hr />quick brown...', + truncator.words(3, '...', html=True )) + truncator = text.Truncator('<br>The <hr/>quick <em>brown fox</em> ' + 'jumped over the lazy dog.') + self.assertEqual('<br>The <hr/>quick <em>brown...</em>', + truncator.words(3, '...', html=True )) + + def test_wrap(self): + digits = '1234 67 9' + self.assertEqual(text.wrap(digits, 100), '1234 67 9') + self.assertEqual(text.wrap(digits, 9), '1234 67 9') + self.assertEqual(text.wrap(digits, 8), '1234 67\n9') + + self.assertEqual(text.wrap('short\na long line', 7), + 'short\na long\nline') + + self.assertEqual(text.wrap('do-not-break-long-words please? ok', 8), + 'do-not-break-long-words\nplease?\nok') + + long_word = 'l%sng' % ('o' * 20) + self.assertEqual(text.wrap(long_word, 20), long_word) + self.assertEqual(text.wrap('a %s word' % long_word, 10), + 'a\n%s\nword' % long_word) + + def test_slugify(self): + items = ( + ('Hello, World!', 'hello-world'), + ('spam & eggs', 'spam-eggs'), + ) + for value, output in items: + self.assertEqual(text.slugify(value), output) diff --git a/tests/utils_tests/test_timesince.py b/tests/utils_tests/test_timesince.py new file mode 100644 index 0000000000..5e641a42c4 --- /dev/null +++ b/tests/utils_tests/test_timesince.py @@ -0,0 +1,124 @@ +from __future__ import unicode_literals + +import datetime +import unittest + +from django.utils.timesince import timesince, timeuntil +from django.utils.tzinfo import LocalTimezone, FixedOffset + +class TimesinceTests(unittest.TestCase): + + def setUp(self): + self.t = datetime.datetime(2007, 8, 14, 13, 46, 0) + self.onemicrosecond = datetime.timedelta(microseconds=1) + self.onesecond = datetime.timedelta(seconds=1) + self.oneminute = datetime.timedelta(minutes=1) + self.onehour = datetime.timedelta(hours=1) + self.oneday = datetime.timedelta(days=1) + self.oneweek = datetime.timedelta(days=7) + self.onemonth = datetime.timedelta(days=30) + self.oneyear = datetime.timedelta(days=365) + + def test_equal_datetimes(self): + """ equal datetimes. """ + self.assertEqual(timesince(self.t, self.t), '0 minutes') + + def test_ignore_microseconds_and_seconds(self): + """ Microseconds and seconds are ignored. """ + self.assertEqual(timesince(self.t, self.t+self.onemicrosecond), + '0 minutes') + self.assertEqual(timesince(self.t, self.t+self.onesecond), + '0 minutes') + + def test_other_units(self): + """ Test other units. """ + self.assertEqual(timesince(self.t, self.t+self.oneminute), + '1 minute') + self.assertEqual(timesince(self.t, self.t+self.onehour), '1 hour') + self.assertEqual(timesince(self.t, self.t+self.oneday), '1 day') + self.assertEqual(timesince(self.t, self.t+self.oneweek), '1 week') + self.assertEqual(timesince(self.t, self.t+self.onemonth), + '1 month') + self.assertEqual(timesince(self.t, self.t+self.oneyear), '1 year') + + def test_multiple_units(self): + """ Test multiple units. """ + self.assertEqual(timesince(self.t, + self.t+2*self.oneday+6*self.onehour), '2 days, 6 hours') + self.assertEqual(timesince(self.t, + self.t+2*self.oneweek+2*self.oneday), '2 weeks, 2 days') + + def test_display_first_unit(self): + """ + If the two differing units aren't adjacent, only the first unit is + displayed. + """ + self.assertEqual(timesince(self.t, + self.t+2*self.oneweek+3*self.onehour+4*self.oneminute), + '2 weeks') + + self.assertEqual(timesince(self.t, + self.t+4*self.oneday+5*self.oneminute), '4 days') + + def test_display_second_before_first(self): + """ + When the second date occurs before the first, we should always + get 0 minutes. + """ + self.assertEqual(timesince(self.t, self.t-self.onemicrosecond), + '0 minutes') + self.assertEqual(timesince(self.t, self.t-self.onesecond), + '0 minutes') + self.assertEqual(timesince(self.t, self.t-self.oneminute), + '0 minutes') + self.assertEqual(timesince(self.t, self.t-self.onehour), + '0 minutes') + self.assertEqual(timesince(self.t, self.t-self.oneday), + '0 minutes') + self.assertEqual(timesince(self.t, self.t-self.oneweek), + '0 minutes') + self.assertEqual(timesince(self.t, self.t-self.onemonth), + '0 minutes') + self.assertEqual(timesince(self.t, self.t-self.oneyear), + '0 minutes') + self.assertEqual(timesince(self.t, + self.t-2*self.oneday-6*self.onehour), '0 minutes') + self.assertEqual(timesince(self.t, + self.t-2*self.oneweek-2*self.oneday), '0 minutes') + self.assertEqual(timesince(self.t, + self.t-2*self.oneweek-3*self.onehour-4*self.oneminute), + '0 minutes') + self.assertEqual(timesince(self.t, + self.t-4*self.oneday-5*self.oneminute), '0 minutes') + + def test_different_timezones(self): + """ When using two different timezones. """ + now = datetime.datetime.now() + now_tz = datetime.datetime.now(LocalTimezone(now)) + now_tz_i = datetime.datetime.now(FixedOffset((3 * 60) + 15)) + + self.assertEqual(timesince(now), '0 minutes') + self.assertEqual(timesince(now_tz), '0 minutes') + self.assertEqual(timeuntil(now_tz, now_tz_i), '0 minutes') + + def test_date_objects(self): + """ Both timesince and timeuntil should work on date objects (#17937). """ + today = datetime.date.today() + self.assertEqual(timesince(today + self.oneday), '0 minutes') + self.assertEqual(timeuntil(today - self.oneday), '0 minutes') + + def test_both_date_objects(self): + """ Timesince should work with both date objects (#9672) """ + today = datetime.date.today() + self.assertEqual(timeuntil(today + self.oneday, today), '1 day') + self.assertEqual(timeuntil(today - self.oneday, today), '0 minutes') + self.assertEqual(timeuntil(today + self.oneweek, today), '1 week') + + def test_naive_datetime_with_tzinfo_attribute(self): + class naive(datetime.tzinfo): + def utcoffset(self, dt): + return None + future = datetime.datetime(2080, 1, 1, tzinfo=naive()) + self.assertEqual(timesince(future), '0 minutes') + past = datetime.datetime(1980, 1, 1, tzinfo=naive()) + self.assertEqual(timeuntil(past), '0 minutes') diff --git a/tests/utils_tests/test_timezone.py b/tests/utils_tests/test_timezone.py new file mode 100644 index 0000000000..650198e8ff --- /dev/null +++ b/tests/utils_tests/test_timezone.py @@ -0,0 +1,63 @@ +import copy +import datetime +import pickle +from django.test.utils import override_settings +from django.utils import timezone +from django.utils.tzinfo import FixedOffset +from django.utils import unittest + + +EAT = FixedOffset(180) # Africa/Nairobi +ICT = FixedOffset(420) # Asia/Bangkok + + +class TimezoneTests(unittest.TestCase): + + def test_localtime(self): + now = datetime.datetime.utcnow().replace(tzinfo=timezone.utc) + local_tz = timezone.LocalTimezone() + local_now = timezone.localtime(now, local_tz) + self.assertEqual(local_now.tzinfo, local_tz) + + def test_now(self): + with override_settings(USE_TZ=True): + self.assertTrue(timezone.is_aware(timezone.now())) + with override_settings(USE_TZ=False): + self.assertTrue(timezone.is_naive(timezone.now())) + + def test_override(self): + default = timezone.get_default_timezone() + try: + timezone.activate(ICT) + + with timezone.override(EAT): + self.assertIs(EAT, timezone.get_current_timezone()) + self.assertIs(ICT, timezone.get_current_timezone()) + + with timezone.override(None): + self.assertIs(default, timezone.get_current_timezone()) + self.assertIs(ICT, timezone.get_current_timezone()) + + timezone.deactivate() + + with timezone.override(EAT): + self.assertIs(EAT, timezone.get_current_timezone()) + self.assertIs(default, timezone.get_current_timezone()) + + with timezone.override(None): + self.assertIs(default, timezone.get_current_timezone()) + self.assertIs(default, timezone.get_current_timezone()) + finally: + timezone.deactivate() + + def test_copy(self): + self.assertIsInstance(copy.copy(timezone.UTC()), timezone.UTC) + self.assertIsInstance(copy.copy(timezone.LocalTimezone()), timezone.LocalTimezone) + + def test_deepcopy(self): + self.assertIsInstance(copy.deepcopy(timezone.UTC()), timezone.UTC) + self.assertIsInstance(copy.deepcopy(timezone.LocalTimezone()), timezone.LocalTimezone) + + def test_pickling_unpickling(self): + self.assertIsInstance(pickle.loads(pickle.dumps(timezone.UTC())), timezone.UTC) + self.assertIsInstance(pickle.loads(pickle.dumps(timezone.LocalTimezone())), timezone.LocalTimezone) diff --git a/tests/utils_tests/test_tzinfo.py b/tests/utils_tests/test_tzinfo.py new file mode 100644 index 0000000000..cec92652cd --- /dev/null +++ b/tests/utils_tests/test_tzinfo.py @@ -0,0 +1,79 @@ +import copy +import datetime +import os +import pickle +import time +from django.utils.tzinfo import FixedOffset, LocalTimezone +from django.utils import unittest + +class TzinfoTests(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.old_TZ = os.environ.get('TZ') + os.environ['TZ'] = 'US/Eastern' + + try: + # Check if a timezone has been set + time.tzset() + cls.tz_tests = True + except AttributeError: + # No timezone available. Don't run the tests that require a TZ + cls.tz_tests = False + + @classmethod + def tearDownClass(cls): + if cls.old_TZ is None: + del os.environ['TZ'] + else: + os.environ['TZ'] = cls.old_TZ + + # Cleanup - force re-evaluation of TZ environment variable. + if cls.tz_tests: + time.tzset() + + def test_fixedoffset(self): + self.assertEqual(repr(FixedOffset(0)), '+0000') + self.assertEqual(repr(FixedOffset(60)), '+0100') + self.assertEqual(repr(FixedOffset(-60)), '-0100') + self.assertEqual(repr(FixedOffset(280)), '+0440') + self.assertEqual(repr(FixedOffset(-280)), '-0440') + self.assertEqual(repr(FixedOffset(-78.4)), '-0118') + self.assertEqual(repr(FixedOffset(78.4)), '+0118') + self.assertEqual(repr(FixedOffset(-5.5*60)), '-0530') + self.assertEqual(repr(FixedOffset(5.5*60)), '+0530') + self.assertEqual(repr(FixedOffset(-.5*60)), '-0030') + self.assertEqual(repr(FixedOffset(.5*60)), '+0030') + + def test_16899(self): + if not self.tz_tests: + return + ts = 1289106000 + # Midnight at the end of DST in US/Eastern: 2010-11-07T05:00:00Z + dt = datetime.datetime.utcfromtimestamp(ts) + # US/Eastern -- we force its representation to "EST" + tz = LocalTimezone(dt + datetime.timedelta(days=1)) + self.assertEqual( + repr(datetime.datetime.fromtimestamp(ts - 3600, tz)), + 'datetime.datetime(2010, 11, 7, 0, 0, tzinfo=EST)') + self.assertEqual( + repr(datetime.datetime.fromtimestamp(ts, tz)), + 'datetime.datetime(2010, 11, 7, 1, 0, tzinfo=EST)') + self.assertEqual( + repr(datetime.datetime.fromtimestamp(ts + 3600, tz)), + 'datetime.datetime(2010, 11, 7, 1, 0, tzinfo=EST)') + + def test_copy(self): + now = datetime.datetime.now() + self.assertIsInstance(copy.copy(FixedOffset(90)), FixedOffset) + self.assertIsInstance(copy.copy(LocalTimezone(now)), LocalTimezone) + + def test_deepcopy(self): + now = datetime.datetime.now() + self.assertIsInstance(copy.deepcopy(FixedOffset(90)), FixedOffset) + self.assertIsInstance(copy.deepcopy(LocalTimezone(now)), LocalTimezone) + + def test_pickling_unpickling(self): + now = datetime.datetime.now() + self.assertIsInstance(pickle.loads(pickle.dumps(FixedOffset(90))), FixedOffset) + self.assertIsInstance(pickle.loads(pickle.dumps(LocalTimezone(now))), LocalTimezone) diff --git a/tests/utils_tests/tests.py b/tests/utils_tests/tests.py new file mode 100644 index 0000000000..8d7fda4800 --- /dev/null +++ b/tests/utils_tests/tests.py @@ -0,0 +1,34 @@ +""" +Tests for django.utils. +""" +from __future__ import absolute_import + +from .test_archive import TestBzip2Tar, TestGzipTar, TestTar, TestZip +from .test_baseconv import TestBaseConv +from .test_checksums import TestUtilsChecksums +from .test_crypto import TestUtilsCryptoMisc, TestUtilsCryptoPBKDF2 +from .test_datastructures import (DictWrapperTests, ImmutableListTests, + MergeDictTests, MultiValueDictTests, SortedDictTests) +from .test_dateformat import DateFormatTests +from .test_dateparse import DateParseTests +from .test_datetime_safe import DatetimeTests +from .test_decorators import DecoratorFromMiddlewareTests +from .test_encoding import TestEncodingUtils +from .test_feedgenerator import FeedgeneratorTest +from .test_functional import FunctionalTestCase +from .test_html import TestUtilsHtml +from .test_http import TestUtilsHttp, ETagProcessingTests, HttpDateProcessingTests +from .test_itercompat import TestIsIterator +from .test_ipv6 import TestUtilsIPv6 +from .test_jslex import JsToCForGettextTest, JsTokensTest +from .test_module_loading import (CustomLoader, DefaultLoader, EggLoader, + ModuleImportTestCase) +from .test_numberformat import TestNumberFormat +from .test_os_utils import SafeJoinTests +from .test_regex_helper import NormalizeTests +from .test_simplelazyobject import TestUtilsSimpleLazyObject +from .test_termcolors import TermColorTests +from .test_text import TestUtilsText +from .test_timesince import TimesinceTests +from .test_timezone import TimezoneTests +from .test_tzinfo import TzinfoTests |
