From b6875ce9e7c86ae0c405e731c111e3d7accd635b Mon Sep 17 00:00:00 2001 From: Artur Barseghyan Date: Tue, 13 Dec 2016 04:03:59 +0100 Subject: [PATCH] prepare 1.6; django 1.9 and 1.10 support --- .coveragerc | 5 + .gitignore | 19 +- .hgignore | 17 +- .pylintrc | 3 + CHANGELOG.rst | 30 +- LICENSE_GPL2.0.txt | 2 +- LICENSE_LGPL_2.1.txt | 2 +- README.rst | 97 ++-- docs/conf.py~ | 312 ------------- docs/index.rst | 97 ++-- example/README.rst | 23 - example/example/local_settings.example | 29 -- example/example/local_settings.py | 29 -- example/example/settings.py | 193 -------- example/requirements_django_1_4.txt | 7 - example/requirements_django_1_5.txt | 7 - example/requirements_django_1_6.txt | 7 - example/requirements_django_1_7.txt | 7 - example/requirements_django_1_8.txt | 7 - examples/README.rst | 30 ++ ...go_admin_timeline_example_app_installer.sh | 9 +- examples/requirements.txt | 1 + examples/requirements/base.txt | 1 + examples/requirements/common.txt | 11 + .../requirements/dev.txt | 0 examples/requirements/django_1_10.txt | 6 + examples/requirements/django_1_4.txt | 6 + examples/requirements/django_1_5.txt | 6 + examples/requirements/django_1_6.txt | 6 + examples/requirements/django_1_7.txt | 6 + examples/requirements/django_1_8.txt | 6 + examples/requirements/django_1_9.txt | 6 + examples/requirements/docs.txt | 1 + examples/requirements/style_checkers.txt | 8 + examples/requirements/testing.txt | 7 + .../example => examples/simple}/__init__.py | 0 .../simple}/foo/__init__.py | 0 .../example => examples/simple}/foo/admin.py | 31 +- .../simple}/foo/management/__init__.py | 0 .../foo/management/commands/__init__.py | 0 .../management/commands/generate_test_data.py | 1 + .../example => examples/simple}/foo/models.py | 45 +- .../example => examples/simple}/manage.py | 0 examples/simple/settings/__init__.py | 1 + examples/simple/settings/base.py | 339 ++++++++++++++ examples/simple/settings/core.py | 14 + examples/simple/settings/dev.py | 1 + .../simple/settings/django_1_7.py | 2 +- .../simple/settings/django_1_8.py | 2 +- examples/simple/settings/docs.py | 1 + .../simple/settings/local_settings.example | 44 ++ examples/simple/settings/local_settings.py | 44 ++ examples/simple/settings/testing.py | 1 + {example/example => examples/simple}/urls.py | 11 +- {example/example => examples/simple}/wsgi.py | 0 pytest.ini | 21 + runtests.py | 15 + scripts/build_docs.sh | 8 +- scripts/clean_up.sh | 4 +- scripts/compile_messages.sh | 4 +- scripts/install.sh | 6 +- scripts/install_django_1_4.sh | 8 +- scripts/install_django_1_5.sh | 8 +- scripts/install_django_1_6.sh | 10 +- scripts/install_django_1_7.sh | 10 +- scripts/install_django_1_8.sh | 10 +- scripts/make_release.sh | 2 +- scripts/prepare_docs.sh | 1 + scripts/pycodestyle.sh | 2 + scripts/pycodestyle_example.sh | 2 + scripts/pylint.sh | 2 + setup.py | 47 +- src/admin_timeline/__init__.py | 6 +- src/admin_timeline/compat.py | 10 +- src/admin_timeline/conf.py | 16 +- src/admin_timeline/defaults.py | 10 +- src/admin_timeline/forms.py | 56 ++- src/admin_timeline/settings.py | 14 +- .../templatetags/admin_timeline_tags.py | 41 +- src/admin_timeline/tests.py | 441 ------------------ src/admin_timeline/tests/__init__.py | 0 src/admin_timeline/tests/base.py | 168 +++++++ src/admin_timeline/tests/data.py | 70 +++ src/admin_timeline/tests/helpers.py | 46 ++ src/admin_timeline/tests/test_core.py | 283 +++++++++++ src/admin_timeline/urls.py | 9 +- src/admin_timeline/views.py | 101 ++-- tox.ini | 59 +-- 88 files changed, 1650 insertions(+), 1380 deletions(-) create mode 100644 .coveragerc create mode 100644 .pylintrc delete mode 100644 docs/conf.py~ delete mode 100644 example/README.rst delete mode 100644 example/example/local_settings.example delete mode 100644 example/example/local_settings.py delete mode 100644 example/example/settings.py delete mode 100644 example/requirements_django_1_4.txt delete mode 100644 example/requirements_django_1_5.txt delete mode 100644 example/requirements_django_1_6.txt delete mode 100644 example/requirements_django_1_7.txt delete mode 100644 example/requirements_django_1_8.txt create mode 100644 examples/README.rst rename {example => examples}/django_admin_timeline_example_app_installer.sh (74%) create mode 100644 examples/requirements.txt create mode 100644 examples/requirements/base.txt create mode 100644 examples/requirements/common.txt rename example/requirements.txt => examples/requirements/dev.txt (100%) create mode 100755 examples/requirements/django_1_10.txt create mode 100644 examples/requirements/django_1_4.txt create mode 100644 examples/requirements/django_1_5.txt create mode 100644 examples/requirements/django_1_6.txt create mode 100644 examples/requirements/django_1_7.txt create mode 100644 examples/requirements/django_1_8.txt create mode 100755 examples/requirements/django_1_9.txt create mode 100644 examples/requirements/docs.txt create mode 100644 examples/requirements/style_checkers.txt create mode 100644 examples/requirements/testing.txt rename {example/example => examples/simple}/__init__.py (100%) rename {example/example => examples/simple}/foo/__init__.py (100%) rename {example/example => examples/simple}/foo/admin.py (77%) rename {example/example => examples/simple}/foo/management/__init__.py (100%) rename {example/example => examples/simple}/foo/management/commands/__init__.py (100%) rename {example/example => examples/simple}/foo/management/commands/generate_test_data.py (99%) rename {example/example => examples/simple}/foo/models.py (62%) rename {example/example => examples/simple}/manage.py (100%) create mode 100644 examples/simple/settings/__init__.py create mode 100644 examples/simple/settings/base.py create mode 100644 examples/simple/settings/core.py create mode 100644 examples/simple/settings/dev.py rename example/example/settings_django_1_7.py => examples/simple/settings/django_1_7.py (95%) rename example/example/settings_django_1_8.py => examples/simple/settings/django_1_8.py (95%) create mode 100644 examples/simple/settings/docs.py create mode 100644 examples/simple/settings/local_settings.example create mode 100644 examples/simple/settings/local_settings.py create mode 100644 examples/simple/settings/testing.py rename {example/example => examples/simple}/urls.py (65%) rename {example/example => examples/simple}/wsgi.py (100%) create mode 100644 pytest.ini create mode 100755 runtests.py create mode 100755 scripts/prepare_docs.sh create mode 100755 scripts/pycodestyle.sh create mode 100755 scripts/pycodestyle_example.sh create mode 100755 scripts/pylint.sh delete mode 100644 src/admin_timeline/tests.py create mode 100644 src/admin_timeline/tests/__init__.py create mode 100644 src/admin_timeline/tests/base.py create mode 100644 src/admin_timeline/tests/data.py create mode 100644 src/admin_timeline/tests/helpers.py create mode 100644 src/admin_timeline/tests/test_core.py diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..18830ef --- /dev/null +++ b/.coveragerc @@ -0,0 +1,5 @@ +[run] +omit = + src/admin_timeline/tests/* + example/simple/settings/* + example/simple/wsgi.py diff --git a/.gitignore b/.gitignore index 3ff3bb6..279c7d1 100644 --- a/.gitignore +++ b/.gitignore @@ -4,14 +4,19 @@ .hg/ .git/ .hgtags +/.coverage +/.cache/ +/geckodriver.log +/ghostdriver.log -/example/db/ -/example/tmp/ -/example/media/static/ -/example/media/_static/ -/example/static/ +/examples/db/ +/examples/tmp/ +/examples/logs/ +/examples/media/static/ +/examples/media/_static/ +/examples/static/ /builddocs/ -builddocs.zip +/builddocs.zip /build/ /dist/ /src/admin_timeline.egg-info @@ -19,4 +24,4 @@ builddocs.zip /MANIFEST.in~ /.tox/ /demos/ -/releases/ \ No newline at end of file +/releases/ diff --git a/.hgignore b/.hgignore index 0460a29..aa4779a 100644 --- a/.hgignore +++ b/.hgignore @@ -3,12 +3,17 @@ syntax: regexp \.hgignore~ \.gitignore~ \.git/ +\.coverage$ +\.cache/v/cache/lastfailed +^geckodriver\.log$ +^ghostdriver\.log$ -^example/db/ -^example/tmp/ -^example/media/static/ -^example/media/_static/ -^example/static/ +^examples/db/ +^examples/tmp/ +^examples/logs/ +^examples/media/static/ +^examples/media/_static/ +^examples/static/ ^builddocs/ ^builddocs.zip ^build/ @@ -18,4 +23,4 @@ syntax: regexp local_settings\.py MANIFEST\.in~ ^\.tox/ -^releases/ \ No newline at end of file +^releases/ diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..f07d866 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,3 @@ +[MASTER] +ignore=migrations,south_migrations,releases + diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9f4437f..30d6ea0 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,5 +1,29 @@ -Release history -=============== +Release history and notes +========================= +`Sequence based identifiers +`_ +are used for versioning (schema follows below): + +.. code-block:: none + + major.minor[.revision] + +- It's always safe to upgrade within the same minor version (for example, from + 0.3 to 0.3.4). +- Minor version changes might be backwards incompatible. Read the + release notes carefully before upgrading (for example, when upgrading from + 0.3.4 to 0.4). +- All backwards incompatible changes are mentioned in this document. + +1.6 +--- +Announcing dropping support of Python 2.6 and Django 1.7. As of 0.9.17 +everything is still backwards compatible with Django 1.7, but in future +versions it will be wiped out. + +- Django 1.9 and 1.8 compatibility. +- pep8 fixes. + 1.5.4 ----- 2015-10-02 @@ -68,4 +92,4 @@ Release history --- 2013-09-09 -- Python 3.3.+ support +- Python 3.3 support diff --git a/LICENSE_GPL2.0.txt b/LICENSE_GPL2.0.txt index 52ed790..3de55fa 100644 --- a/LICENSE_GPL2.0.txt +++ b/LICENSE_GPL2.0.txt @@ -291,7 +291,7 @@ convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. django-admin-timeline - A Facebook-like timeline app for Django admin. - Copyright (C) 2013-2015 Artur Barseghyan + Copyright (C) 2013-2016 Artur Barseghyan This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/LICENSE_LGPL_2.1.txt b/LICENSE_LGPL_2.1.txt index 5b903ce..dc95cd0 100644 --- a/LICENSE_LGPL_2.1.txt +++ b/LICENSE_LGPL_2.1.txt @@ -471,7 +471,7 @@ convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. django-admin-timeline - A Facebook-like timeline app for Django admin. - Copyright (C) 2013-2015 Artur Barseghyan + Copyright (C) 2013-2016 Artur Barseghyan This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public diff --git a/README.rst b/README.rst index 27bcca6..ade57ec 100644 --- a/README.rst +++ b/README.rst @@ -1,39 +1,61 @@ -=================================== +===================== django-admin-timeline -=================================== +===================== A Facebook-like timeline app for Django admin. It's very similar to built-in feature `Daily progress`, but then has a nicer templates and infinite scroll implemented. Actions are broken up by day, then by action. Filtering by user (multiple select) and content type (multiple select) is implemented. Prerequisites -=================================== -- Django 1.4, 1.5, 1.6, 1.7, 1.8 -- Python >=2.6.8, 2.7, 3.3 +============= +Present +------- +Starting from ``django-admin-timeline`` 1.7: + +- Django 1.8, 1.9, 1.10 +- Python 2.7, 3.4, 3.5 + +Past +---- +Current version of ``django-admin-timeline`` (1.6) has the following +prerequisites: + +- Django 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 1.10 +- Python >=2.6.8, 2.7, 3.3, 3.4, 3.5 + +Dropping support of Django 1.4, 1.5, 1.6 and 1.7 has been announced in +version 1.6. As of 1.6 everything is still backwards compatible with +versions 1.4, 1.5, 1.6 and 1.7, but in future versions compatibility with +these versions will be wiped out. + +Dropping support of Python 2.6 and 3.3 has been announced in version 1.6. +As of 1.6 everything is still backwards compatible with Python 2.6 and 3.3, +but in future versions compatibility with these versions will be wiped out. Installation -=================================== -1. Install in your virtual environemnt +============ +1. Install in your virtual environment Latest stable version from PyPI: -.. code-block:: none +.. code-block:: sh - $ pip install django-admin-timeline + pip install django-admin-timeline Latest stable version from bitbucket: -.. code-block:: none +.. code-block:: sh - $ pip install -e hg+http://bitbucket.org/barseghyanartur/django-admin-timeline@stable#egg=django-admin-timeline + pip install -e hg+http://bitbucket.org/barseghyanartur/django-admin-timeline@stable#egg=django-admin-timeline Latest stable version from github: -.. code-block:: none +.. code-block:: sh - $ pip install -e git+https://github.com/barseghyanartur/django-admin-timeline@stable#egg=django-admin-timeline + pip install https://github.com/barseghyanartur/django-admin-timeline/archive/stable.tar.gz -3. Add `admin_timeline` to your `INSTALLED_APPS` in the global settings.py. +3. Add ``admin_timeline`` to your ``INSTALLED_APPS`` in the + global ``settings.py``. .. code-block:: python @@ -43,14 +65,15 @@ Latest stable version from github: # ... ) -4. Collect the static files by running (see the Troubleshooting section in case of problems): +4. Collect the static files by running (see the Troubleshooting section in + case of problems): -.. code-block:: none +.. code-block:: sh - $ ./manage.py collectstatic + ./manage.py collectstatic 5. Override app settings in your global `settings` module (see the - `apps.admin_timeline.defaults` for the list of settings). As for now, most + ``apps.admin_timeline.defaults`` for the list of settings). As for now, most important of those is ``NUMBER_OF_ENTRIES_PER_PAGE`` - number of entries displayed per page (for both non-AJAX and AJAX requests). @@ -63,9 +86,9 @@ Latest stable version from github: url(r'^admin/', include(admin.site.urls)), Demo -=================================== +==== Live demo ------------------------------------ +--------- See the `live demo app `_ on Heroku. @@ -75,26 +98,26 @@ Credentials: - password: test Run demo locally ------------------------------------ -In order to be able to quickly evaluate the `django-admin-timeline`, a demo +---------------- +In order to be able to quickly evaluate the ``django-admin-timeline``, a demo app (with a quick installer) has been created (works on Ubuntu/Debian, may work on other Linux systems as well, although not guaranteed). Follow the instructions below for having the demo running within a minute. -Grab the latest `django_admin_timeline_example_app_installer.sh`: +Grab the latest ``django_admin_timeline_example_app_installer.sh``: -.. code-block:: none +.. code-block:: sh - $ wget https://raw.github.com/barseghyanartur/django-admin-timeline/stable/example/django_admin_timeline_example_app_installer.sh + wget https://raw.github.com/barseghyanartur/django-admin-timeline/stable/example/django_admin_timeline_example_app_installer.sh Assign execute rights to the installer and run the -`django_admin_timeline_example_app_installer.sh`: +``django_admin_timeline_example_app_installer.sh``: -.. code-block:: none +.. code-block:: sh - $ chmod +x django_admin_timeline_example_app_installer.sh + chmod +x django_admin_timeline_example_app_installer.sh - $ ./django_admin_timeline_example_app_installer.sh + ./django_admin_timeline_example_app_installer.sh Open your browser and test the app. @@ -107,18 +130,18 @@ If quick installer doesn't work for you, see the manual steps on running the `_. Troubleshooting -=================================== +=============== If somehow static files are not collected properly (missing admin_timeline.js and admin_timeline.css files), install the latest stable version from source. -.. code-block:: none +.. code-block:: sh $ pip install -e hg+http://bitbucket.org/barseghyanartur/django-admin-timeline@stable#egg=django-admin-timeline Usage -=================================== +===== After following all installation steps, you should be able to access the -`django-admin-timeline` by: +``django-admin-timeline`` by: http://127.0.0.1:8000/admin/timeline/ @@ -127,13 +150,13 @@ An example application is available. See the following directory: https://github.com/barseghyanartur/django-admin-timeline/tree/stable/example License -=================================== +======= GPL 2.0/LGPL 2.1 Support -=================================== -For any issues contact me at the e-mail given in the `Author` section. +======= +For any issues contact me at the e-mail given in the `Author`_ section. Author -=================================== +====== Artur Barseghyan diff --git a/docs/conf.py~ b/docs/conf.py~ deleted file mode 100644 index fc726ef..0000000 --- a/docs/conf.py~ +++ /dev/null @@ -1,312 +0,0 @@ -# -*- coding: utf-8 -*- -# -# admin_timeline documentation build configuration file, created by -# sphinx-quickstart on Sun May 12 18:49:30 2013. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) -sys.path.insert(0, os.path.abspath('../src')) - -# -- Django configuration ------------------------------------------------------ -from django.conf import settings -if not settings.configured: - settings.configure() - -# -- General configuration ----------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'admin_timeline' -copyright = u'2013, Artur Barseghyan ' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '0.4' -# The full version, including alpha/beta/rc tags. -release = '0.4' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'default' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'admin_timelinedoc' - - -# -- Options for LaTeX output -------------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'admin_timeline.tex', u'admin\\_timeline Documentation', - u'Artur Barseghyan \\textless{}artur.barseghyan@gmail.com\\textgreater{}', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output -------------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'admin_timeline', u'admin_timeline Documentation', - [u'Artur Barseghyan '], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------------ - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'admin_timeline', u'admin_timeline Documentation', - u'Artur Barseghyan ', 'admin_timeline', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False - - -# -- Options for Epub output --------------------------------------------------- - -# Bibliographic Dublin Core info. -epub_title = u'admin_timeline' -epub_author = u'Artur Barseghyan ' -epub_publisher = u'Artur Barseghyan ' -epub_copyright = u'2013, Artur Barseghyan ' - -# The language of the text. It defaults to the language option -# or en if the language is not set. -#epub_language = '' - -# The scheme of the identifier. Typical schemes are ISBN or URL. -#epub_scheme = '' - -# The unique identifier of the text. This can be a ISBN number -# or the project homepage. -#epub_identifier = '' - -# A unique identification for the text. -#epub_uid = '' - -# A tuple containing the cover image and cover page html template filenames. -#epub_cover = () - -# A sequence of (type, uri, title) tuples for the guide element of content.opf. -#epub_guide = () - -# HTML files that should be inserted before the pages created by sphinx. -# The format is a list of tuples containing the path and title. -#epub_pre_files = [] - -# HTML files shat should be inserted after the pages created by sphinx. -# The format is a list of tuples containing the path and title. -#epub_post_files = [] - -# A list of files that should not be packed into the epub file. -#epub_exclude_files = [] - -# The depth of the table of contents in toc.ncx. -#epub_tocdepth = 3 - -# Allow duplicate toc entries. -#epub_tocdup = True - -# Fix unsupported image types using the PIL. -#epub_fix_images = False - -# Scale large images. -#epub_max_image_width = 0 - -# If 'no', URL addresses will not be shown. -#epub_show_urls = 'inline' - -# If false, no index is generated. -#epub_use_index = True diff --git a/docs/index.rst b/docs/index.rst index 679e303..d7b0f91 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,39 +1,61 @@ -=================================== +===================== django-admin-timeline -=================================== +===================== A Facebook-like timeline app for Django admin. It's very similar to built-in feature `Daily progress`, but then has a nicer templates and infinite scroll implemented. Actions are broken up by day, then by action. Filtering by user (multiple select) and content type (multiple select) is implemented. Prerequisites -=================================== -- Django 1.4, 1.5, 1.6, 1.7, 1.8 -- Python >=2.6.8, 2.7, 3.3 +============= +Present +------- +Starting from ``django-admin-timeline`` 1.7: + +- Django 1.8, 1.9, 1.10 +- Python 2.7, 3.4, 3.5 + +Past +---- +Current version of ``django-admin-timeline`` (1.6) has the following +prerequisites: + +- Django 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 1.10 +- Python >=2.6.8, 2.7, 3.3, 3.4, 3.5 + +Dropping support of Django 1.4, 1.5, 1.6 and 1.7 has been announced in +version 1.6. As of 1.6 everything is still backwards compatible with +versions 1.4, 1.5, 1.6 and 1.7, but in future versions compatibility with +these versions will be wiped out. + +Dropping support of Python 2.6 and 3.3 has been announced in version 1.6. +As of 1.6 everything is still backwards compatible with Python 2.6 and 3.3, +but in future versions compatibility with these versions will be wiped out. Installation -=================================== -1. Install in your virtual environemnt +============ +1. Install in your virtual environment Latest stable version from PyPI: -.. code-block:: none +.. code-block:: sh - $ pip install django-admin-timeline + pip install django-admin-timeline Latest stable version from bitbucket: -.. code-block:: none +.. code-block:: sh - $ pip install -e hg+http://bitbucket.org/barseghyanartur/django-admin-timeline@stable#egg=django-admin-timeline + pip install -e hg+http://bitbucket.org/barseghyanartur/django-admin-timeline@stable#egg=django-admin-timeline Latest stable version from github: -.. code-block:: none +.. code-block:: sh - $ pip install -e git+https://github.com/barseghyanartur/django-admin-timeline@stable#egg=django-admin-timeline + pip install https://github.com/barseghyanartur/django-admin-timeline/archive/stable.tar.gz -3. Add `admin_timeline` to your `INSTALLED_APPS` in the global settings.py. +3. Add ``admin_timeline`` to your ``INSTALLED_APPS`` in the + global ``settings.py``. .. code-block:: python @@ -43,14 +65,15 @@ Latest stable version from github: # ... ) -4. Collect the static files by running (see the Troubleshooting section in case of problems): +4. Collect the static files by running (see the Troubleshooting section in + case of problems): -.. code-block:: none +.. code-block:: sh - $ ./manage.py collectstatic + ./manage.py collectstatic 5. Override app settings in your global `settings` module (see the - `apps.admin_timeline.defaults` for the list of settings). As for now, most + ``apps.admin_timeline.defaults`` for the list of settings). As for now, most important of those is ``NUMBER_OF_ENTRIES_PER_PAGE`` - number of entries displayed per page (for both non-AJAX and AJAX requests). @@ -63,9 +86,9 @@ Latest stable version from github: url(r'^admin/', include(admin.site.urls)), Demo -=================================== +==== Live demo ------------------------------------ +--------- See the `live demo app `_ on Heroku. @@ -75,26 +98,26 @@ Credentials: - password: test Run demo locally ------------------------------------ -In order to be able to quickly evaluate the `django-admin-timeline`, a demo +---------------- +In order to be able to quickly evaluate the ``django-admin-timeline``, a demo app (with a quick installer) has been created (works on Ubuntu/Debian, may work on other Linux systems as well, although not guaranteed). Follow the instructions below for having the demo running within a minute. -Grab the latest `django_admin_timeline_example_app_installer.sh`: +Grab the latest ``django_admin_timeline_example_app_installer.sh``: -.. code-block:: none +.. code-block:: sh - $ wget https://raw.github.com/barseghyanartur/django-admin-timeline/stable/example/django_admin_timeline_example_app_installer.sh + wget https://raw.github.com/barseghyanartur/django-admin-timeline/stable/example/django_admin_timeline_example_app_installer.sh Assign execute rights to the installer and run the -`django_admin_timeline_example_app_installer.sh`: +``django_admin_timeline_example_app_installer.sh``: -.. code-block:: none +.. code-block:: sh - $ chmod +x django_admin_timeline_example_app_installer.sh + chmod +x django_admin_timeline_example_app_installer.sh - $ ./django_admin_timeline_example_app_installer.sh + ./django_admin_timeline_example_app_installer.sh Open your browser and test the app. @@ -107,18 +130,18 @@ If quick installer doesn't work for you, see the manual steps on running the `_. Troubleshooting -=================================== +=============== If somehow static files are not collected properly (missing admin_timeline.js and admin_timeline.css files), install the latest stable version from source. -.. code-block:: none +.. code-block:: sh $ pip install -e hg+http://bitbucket.org/barseghyanartur/django-admin-timeline@stable#egg=django-admin-timeline Usage -=================================== +===== After following all installation steps, you should be able to access the -`django-admin-timeline` by: +``django-admin-timeline`` by: http://127.0.0.1:8000/admin/timeline/ @@ -127,15 +150,15 @@ An example application is available. See the following directory: https://github.com/barseghyanartur/django-admin-timeline/tree/stable/example License -=================================== +======= GPL 2.0/LGPL 2.1 Support -=================================== -For any issues contact me at the e-mail given in the `Author` section. +======= +For any issues contact me at the e-mail given in the `Author`_ section. Author -=================================== +====== Artur Barseghyan Screenshots diff --git a/example/README.rst b/example/README.rst deleted file mode 100644 index f5be422..0000000 --- a/example/README.rst +++ /dev/null @@ -1,23 +0,0 @@ -Installation -============================ -$ pip install -r requirements.txt - -$ cp local_settings.example local_settings.py - -$ ./manage.py syncdb --noinput - -$ ./manage.py collectstatic --noinput - -$ ./manage.py generate_test_data - -$ ./manage.py runserver - -Usage -============================ -After following all installation steps, you should be able to access the admin-timeline by: - - http://127.0.0.1:8000/admin/timeline/ - -Admin credentials: - - admin:test diff --git a/example/example/local_settings.example b/example/example/local_settings.example deleted file mode 100644 index a647a8f..0000000 --- a/example/example/local_settings.example +++ /dev/null @@ -1,29 +0,0 @@ -# Django settings for resato_portal project. -import os -PROJECT_DIR = lambda base : os.path.abspath(os.path.join(os.path.dirname(__file__), base).replace('\\','/')) - -DEBUG = True -DEBUG_TOOLBAR = not True -TEMPLATE_DEBUG = DEBUG -DEV = False - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. - 'NAME': PROJECT_DIR('../db/example.db'), # Or path to database file if using sqlite3. - # The following settings are not used with sqlite3: - 'USER': '', - 'PASSWORD': '', - 'HOST': '', # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP. - 'PORT': '', # Set to empty string for default. - } -} - -INTERNAL_IPS = ('127.0.0.1',) - -EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend' -EMAIL_FILE_PATH = PROJECT_DIR('../tmp') - -DEFAULT_FROM_EMAIL = '' - -ADMIN_TIMELINE_NUMBER_OF_ENTRIES_PER_PAGE = 4 diff --git a/example/example/local_settings.py b/example/example/local_settings.py deleted file mode 100644 index 3e44cd5..0000000 --- a/example/example/local_settings.py +++ /dev/null @@ -1,29 +0,0 @@ -# Django settings for resato_portal project. -import os -PROJECT_DIR = lambda base : os.path.abspath(os.path.join(os.path.dirname(__file__), base).replace('\\','/')) - -DEBUG = True -DEBUG_TOOLBAR = not True -TEMPLATE_DEBUG = DEBUG -DEV = False - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. - 'NAME': PROJECT_DIR(os.path.join('..', 'db', 'example.db')), # Or path to database file if using sqlite3. - # The following settings are not used with sqlite3: - 'USER': '', - 'PASSWORD': '', - 'HOST': '', # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP. - 'PORT': '', # Set to empty string for default. - } -} - -INTERNAL_IPS = ('127.0.0.1',) - -EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend' -EMAIL_FILE_PATH = PROJECT_DIR(os.path.join('..', 'tmp')) - -DEFAULT_FROM_EMAIL = '' - -ADMIN_TIMELINE_NUMBER_OF_ENTRIES_PER_PAGE = 4 diff --git a/example/example/settings.py b/example/example/settings.py deleted file mode 100644 index 9b27a42..0000000 --- a/example/example/settings.py +++ /dev/null @@ -1,193 +0,0 @@ -# Django settings for example project. -import os -PROJECT_DIR = lambda base : os.path.abspath(os.path.join(os.path.dirname(__file__), base).replace('\\','/')) -gettext = lambda s: s - -DEBUG = False -DEBUG_TOOLBAR = False -TEMPLATE_DEBUG = DEBUG -DEV = False - -ADMINS = ( - # ('Your Name', 'your_email@example.com'), -) - -MANAGERS = ADMINS - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. - 'NAME': PROJECT_DIR(os.path.join('..', 'db', 'example.db')), # Or path to database file if using sqlite3. - # The following settings are not used with sqlite3: - 'USER': '', - 'PASSWORD': '', - 'HOST': '', # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP. - 'PORT': '', # Set to empty string for default. - } -} - -# Hosts/domain names that are valid for this site; required if DEBUG is False -# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts -ALLOWED_HOSTS = [] - -# Local time zone for this installation. Choices can be found here: -# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name -# although not all choices may be available on all operating systems. -# In a Windows environment this must be set to your system time zone. -TIME_ZONE = 'America/Chicago' - -# Language code for this installation. All choices can be found here: -# http://www.i18nguy.com/unicode/language-identifiers.html -LANGUAGE_CODE = 'en-us' - -SITE_ID = 1 - -# If you set this to False, Django will make some optimizations so as not -# to load the internationalization machinery. -USE_I18N = True - -# If you set this to False, Django will not format dates, numbers and -# calendars according to the current locale. -USE_L10N = True - -# If you set this to False, Django will not use timezone-aware datetimes. -USE_TZ = True - -# Absolute filesystem path to the directory that will hold user-uploaded files. -# Example: "/var/www/example.com/media/" -MEDIA_ROOT = PROJECT_DIR(os.path.join('..', 'media')) - -# URL that handles the media served from MEDIA_ROOT. Make sure to use a -# trailing slash. -# Examples: "http://example.com/media/", "http://media.example.com/" -MEDIA_URL = '/media/' - -# Absolute path to the directory static files should be collected to. -# Don't put anything in this directory yourself; store your static files -# in apps' "static/" subdirectories and in STATICFILES_DIRS. -# Example: "/var/www/example.com/static/" -STATIC_ROOT = PROJECT_DIR(os.path.join('..', 'static')) - -# URL prefix for static files. -# Example: "http://example.com/static/", "http://static.example.com/" -STATIC_URL = '/static/' - -# Additional locations of static files -STATICFILES_DIRS = ( - # Put strings here, like "/home/html/static" or "C:/www/django/static". - # Always use forward slashes, even on Windows. - # Don't forget to use absolute paths, not relative paths. - PROJECT_DIR(os.path.join('..', 'media', 'static')), -) - -# List of finder classes that know how to find static files in -# various locations. -STATICFILES_FINDERS = ( - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', -# 'django.contrib.staticfiles.finders.DefaultStorageFinder', -) - -# Make this unique, and don't share it with anybody. -SECRET_KEY = '6sf18c*w971i8a-m^1coasrmur2k6+q5_kyn*)s@(*_dk5q3&r' - -# List of callables that know how to import templates from various sources. -TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', - 'django.template.loaders.eggs.Loader', -) - -MIDDLEWARE_CLASSES = ( - 'django.middleware.common.CommonMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - # Uncomment the next line for simple clickjacking protection: - # 'django.middleware.clickjacking.XFrameOptionsMiddleware', -) - -ROOT_URLCONF = 'urls' - -# Python dotted path to the WSGI application used by Django's runserver. -WSGI_APPLICATION = 'wsgi.application' - -TEMPLATE_DIRS = ( - # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". - # Always use forward slashes, even on Windows. - # Don't forget to use absolute paths, not relative paths. - PROJECT_DIR('templates'), -) - -INSTALLED_APPS = ( - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.sites', - 'django.contrib.messages', - 'django.contrib.staticfiles', - # Uncomment the next line to enable the admin: - 'django.contrib.admin', - # Uncomment the next line to enable admin documentation: - # 'django.contrib.admindocs', - - 'admin_timeline', # admin-timeline - - 'foo', # Example app to generate test data -) - -# A sample logging configuration. The only tangible logging -# performed by this configuration is to send an email to -# the site admins on every HTTP 500 error when DEBUG=False. -# See http://docs.djangoproject.com/en/dev/topics/logging for -# more details on how to customize your logging configuration. -LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'filters': { - 'require_debug_false': { - '()': 'django.utils.log.RequireDebugFalse' - } - }, - 'handlers': { - 'mail_admins': { - 'level': 'ERROR', - 'filters': ['require_debug_false'], - 'class': 'django.utils.log.AdminEmailHandler' - } - }, - 'loggers': { - 'django.request': { - 'handlers': ['mail_admins'], - 'level': 'ERROR', - 'propagate': True, - }, - } -} - - -# Do not put any settings below this line -try: - from local_settings import * -except: - pass - -# Make the `django-admin-timeline` package available without installation. -if DEV: - import sys - sys.path.insert(0, os.path.abspath('../../src')) - -if DEBUG and DEBUG_TOOLBAR: - # debug_toolbar - MIDDLEWARE_CLASSES += ( - 'debug_toolbar.middleware.DebugToolbarMiddleware', - ) - - INSTALLED_APPS += ( - 'debug_toolbar', - ) - - DEBUG_TOOLBAR_CONFIG = { - 'INTERCEPT_REDIRECTS': False, - } diff --git a/example/requirements_django_1_4.txt b/example/requirements_django_1_4.txt deleted file mode 100644 index f4a17ce..0000000 --- a/example/requirements_django_1_4.txt +++ /dev/null @@ -1,7 +0,0 @@ -Django>=1.4,<1.5 -Sphinx -Pillow -python-dateutil -radar -selenium -tox diff --git a/example/requirements_django_1_5.txt b/example/requirements_django_1_5.txt deleted file mode 100644 index 4f6b4db..0000000 --- a/example/requirements_django_1_5.txt +++ /dev/null @@ -1,7 +0,0 @@ -Django>=1.5,<1.6 -Sphinx -Pillow -python-dateutil -radar -selenium -tox diff --git a/example/requirements_django_1_6.txt b/example/requirements_django_1_6.txt deleted file mode 100644 index f6f5263..0000000 --- a/example/requirements_django_1_6.txt +++ /dev/null @@ -1,7 +0,0 @@ -Django>=1.6,<1.7 -Sphinx -Pillow -python-dateutil -radar -selenium -tox diff --git a/example/requirements_django_1_7.txt b/example/requirements_django_1_7.txt deleted file mode 100644 index 7e54972..0000000 --- a/example/requirements_django_1_7.txt +++ /dev/null @@ -1,7 +0,0 @@ -Django>=1.7,<1.8 -Sphinx -Pillow -python-dateutil -radar -selenium -tox diff --git a/example/requirements_django_1_8.txt b/example/requirements_django_1_8.txt deleted file mode 100644 index 806807f..0000000 --- a/example/requirements_django_1_8.txt +++ /dev/null @@ -1,7 +0,0 @@ -Django>=1.8,<1.9 -Sphinx -Pillow -python-dateutil -radar -selenium -tox diff --git a/examples/README.rst b/examples/README.rst new file mode 100644 index 0000000..661b0a5 --- /dev/null +++ b/examples/README.rst @@ -0,0 +1,30 @@ +Installation +============ +.. code-block:: sh + + pip install -r requirements.txt + + cp local_settings.example local_settings.py + + ./manage.py syncdb --noinput + + ./manage.py collectstatic --noinput + + ./manage.py generate_test_data + + ./manage.py runserver + +Usage +===== +After following all installation steps, you should be able to access the +``django-admin-timeline`` by: + +.. code-block:: text + + http://127.0.0.1:8000/admin/timeline/ + +Admin credentials: + +.. code-block:: text + + admin:test diff --git a/example/django_admin_timeline_example_app_installer.sh b/examples/django_admin_timeline_example_app_installer.sh similarity index 74% rename from example/django_admin_timeline_example_app_installer.sh rename to examples/django_admin_timeline_example_app_installer.sh index 2ee895e..c5caf1a 100755 --- a/example/django_admin_timeline_example_app_installer.sh +++ b/examples/django_admin_timeline_example_app_installer.sh @@ -3,19 +3,18 @@ virtualenv admin_timeline source admin_timeline/bin/activate mkdir django_admin_timeline_example_app_installer/ tar -xvf django_admin_timeline_example_app_installer.tar.gz -C django_admin_timeline_example_app_installer -cd django_admin_timeline_example_app_installer/django-admin-timeline-stable/example/example/ -pip install Django +cd django_admin_timeline_example_app_installer/django-admin-timeline-stable/examples/simple/ pip install -r ../requirements.txt -pip install -e git+https://github.com/barseghyanartur/django-admin-timeline@stable#egg=django-admin-timeline +pip install https://github.com/barseghyanartur/django-admin-timeline/archive/stable.tar.gz mkdir ../media/ mkdir ../media/static/ mkdir ../static/ mkdir ../db/ mkdir ../logs/ mkdir ../tmp/ -cp local_settings.example local_settings.py +cp settings/local_settings.example settings/local_settings.py ./manage.py syncdb --noinput --traceback -v 3 ./manage.py migrate --noinput ./manage.py collectstatic --noinput --traceback -v 3 ./manage.py generate_test_data --traceback -v 3 -./manage.py runserver 0.0.0.0:8001 --traceback -v 3 \ No newline at end of file +./manage.py runserver 0.0.0.0:8001 --traceback -v 3 diff --git a/examples/requirements.txt b/examples/requirements.txt new file mode 100644 index 0000000..b0c3540 --- /dev/null +++ b/examples/requirements.txt @@ -0,0 +1 @@ +-r requirements/dev.txt diff --git a/examples/requirements/base.txt b/examples/requirements/base.txt new file mode 100644 index 0000000..97ceaae --- /dev/null +++ b/examples/requirements/base.txt @@ -0,0 +1 @@ +-r django_1_8.txt diff --git a/examples/requirements/common.txt b/examples/requirements/common.txt new file mode 100644 index 0000000..dc83b7c --- /dev/null +++ b/examples/requirements/common.txt @@ -0,0 +1,11 @@ +Jinja2 +MarkupSafe +Sphinx +docutils +ipdb +ipython +selenium==2.53.6 + +Pillow +python-dateutil +radar diff --git a/example/requirements.txt b/examples/requirements/dev.txt similarity index 100% rename from example/requirements.txt rename to examples/requirements/dev.txt diff --git a/examples/requirements/django_1_10.txt b/examples/requirements/django_1_10.txt new file mode 100755 index 0000000..732359f --- /dev/null +++ b/examples/requirements/django_1_10.txt @@ -0,0 +1,6 @@ +-r common.txt +-r testing.txt +-r docs.txt + +Django>=1.10,<1.11 +sqlparse==0.2.2 diff --git a/examples/requirements/django_1_4.txt b/examples/requirements/django_1_4.txt new file mode 100644 index 0000000..ee3e522 --- /dev/null +++ b/examples/requirements/django_1_4.txt @@ -0,0 +1,6 @@ +-r common.txt +-r testing.txt +-r docs.txt + +Django>=1.4,<1.5 +sqlparse==0.1.9 diff --git a/examples/requirements/django_1_5.txt b/examples/requirements/django_1_5.txt new file mode 100644 index 0000000..dba9021 --- /dev/null +++ b/examples/requirements/django_1_5.txt @@ -0,0 +1,6 @@ +-r common.txt +-r testing.txt +-r docs.txt + +Django>=1.5,<1.6 +sqlparse==0.1.9 diff --git a/examples/requirements/django_1_6.txt b/examples/requirements/django_1_6.txt new file mode 100644 index 0000000..d1acc75 --- /dev/null +++ b/examples/requirements/django_1_6.txt @@ -0,0 +1,6 @@ +-r common.txt +-r testing.txt +-r docs.txt + +Django>=1.6,<1.7 +sqlparse==0.1.9 diff --git a/examples/requirements/django_1_7.txt b/examples/requirements/django_1_7.txt new file mode 100644 index 0000000..4242332 --- /dev/null +++ b/examples/requirements/django_1_7.txt @@ -0,0 +1,6 @@ +-r common.txt +-r testing.txt +-r docs.txt + +Django>=1.7,<1.8 +sqlparse==0.1.9 diff --git a/examples/requirements/django_1_8.txt b/examples/requirements/django_1_8.txt new file mode 100644 index 0000000..2a7dd66 --- /dev/null +++ b/examples/requirements/django_1_8.txt @@ -0,0 +1,6 @@ +-r common.txt +-r testing.txt +-r docs.txt + +Django>=1.8,<1.9 +sqlparse==0.1.9 diff --git a/examples/requirements/django_1_9.txt b/examples/requirements/django_1_9.txt new file mode 100755 index 0000000..5486942 --- /dev/null +++ b/examples/requirements/django_1_9.txt @@ -0,0 +1,6 @@ +-r common.txt +-r testing.txt +-r docs.txt + +Django>=1.9,<1.10 +sqlparse==0.2.2 diff --git a/examples/requirements/docs.txt b/examples/requirements/docs.txt new file mode 100644 index 0000000..2806c16 --- /dev/null +++ b/examples/requirements/docs.txt @@ -0,0 +1 @@ +Sphinx diff --git a/examples/requirements/style_checkers.txt b/examples/requirements/style_checkers.txt new file mode 100644 index 0000000..92b6355 --- /dev/null +++ b/examples/requirements/style_checkers.txt @@ -0,0 +1,8 @@ +# Style checkers +pydocstyle +pylint +flake8 +isort +pre-commit +check-manifest +restview diff --git a/examples/requirements/testing.txt b/examples/requirements/testing.txt new file mode 100644 index 0000000..5fdb65d --- /dev/null +++ b/examples/requirements/testing.txt @@ -0,0 +1,7 @@ +factory_boy==2.7.0 +fake-factory==0.7.2 +pytest==3.0.2 +pytest-django==2.9.1 +pytest-cov==2.2.1 +radar +tox==2.1.1 diff --git a/example/example/__init__.py b/examples/simple/__init__.py similarity index 100% rename from example/example/__init__.py rename to examples/simple/__init__.py diff --git a/example/example/foo/__init__.py b/examples/simple/foo/__init__.py similarity index 100% rename from example/example/foo/__init__.py rename to examples/simple/foo/__init__.py diff --git a/example/example/foo/admin.py b/examples/simple/foo/admin.py similarity index 77% rename from example/example/foo/admin.py rename to examples/simple/foo/admin.py index 4af0986..5d60878 100644 --- a/example/example/foo/admin.py +++ b/examples/simple/foo/admin.py @@ -1,13 +1,12 @@ from django.contrib import admin from django.utils.translation import ugettext_lazy as _ -from foo.models import FooItem, Foo2Item, Foo3Item, Foo4Item +from .models import FooItem, Foo2Item, Foo3Item, Foo4Item class FooItemBaseAdmin(admin.ModelAdmin): - """ - Foo item admin. - """ + """FooItem base admin.""" + list_display = ('title', 'date_published') prepopulated_fields = {'slug': ('title',)} list_filter = ('date_published',) @@ -23,37 +22,59 @@ class FooItemBaseAdmin(admin.ModelAdmin): }), (_("Additional"), { 'classes': ('collapse',), - 'fields': ('date_created', 'date_updated') #, + 'fields': ('date_created', 'date_updated') }) ) class Meta: + """Meta.""" + app_label = _('Foo item') class FooItemAdmin(FooItemBaseAdmin): + """FooItem admin.""" + class Meta: + """Meta.""" + app_label = _('Foo item') + admin.site.register(FooItem, FooItemAdmin) class Foo2ItemAdmin(FooItemBaseAdmin): + """Foo2Item admin.""" + class Meta: + """Meta.""" + app_label = _('Foo 2 item') + admin.site.register(Foo2Item, Foo2ItemAdmin) class Foo3ItemAdmin(FooItemBaseAdmin): + """Foo3Item admin.""" + class Meta: + """Meta.""" + app_label = _('Foo 3 item') + admin.site.register(Foo3Item, Foo3ItemAdmin) class Foo4ItemAdmin(FooItemBaseAdmin): + """Foo4Item admin.""" + class Meta: + """Meta.""" + app_label = _('Foo 4 item') + admin.site.register(Foo4Item, Foo4ItemAdmin) diff --git a/example/example/foo/management/__init__.py b/examples/simple/foo/management/__init__.py similarity index 100% rename from example/example/foo/management/__init__.py rename to examples/simple/foo/management/__init__.py diff --git a/example/example/foo/management/commands/__init__.py b/examples/simple/foo/management/commands/__init__.py similarity index 100% rename from example/example/foo/management/commands/__init__.py rename to examples/simple/foo/management/commands/__init__.py diff --git a/example/example/foo/management/commands/generate_test_data.py b/examples/simple/foo/management/commands/generate_test_data.py similarity index 99% rename from example/example/foo/management/commands/generate_test_data.py rename to examples/simple/foo/management/commands/generate_test_data.py index 484cd16..9a524b7 100644 --- a/example/example/foo/management/commands/generate_test_data.py +++ b/examples/simple/foo/management/commands/generate_test_data.py @@ -2,6 +2,7 @@ from admin_timeline.tests import generate_data + class Command(BaseCommand): def handle(self, *args, **options): generate_data() diff --git a/example/example/foo/models.py b/examples/simple/foo/models.py similarity index 62% rename from example/example/foo/models.py rename to examples/simple/foo/models.py index bd4fee5..266d4fc 100644 --- a/example/example/foo/models.py +++ b/examples/simple/foo/models.py @@ -3,41 +3,72 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ +import six + + +@six.python_2_unicode_compatible class FooItemBase(models.Model): - """ - Foo item base. - """ + """Foo item base.""" + title = models.CharField(_("Title"), max_length=100) slug = models.SlugField(_("Slug"), unique=True) body = models.TextField(_("Body")) - date_published = models.DateTimeField(_("Date published"), blank=True, null=True, default=datetime.datetime.now()) - date_created = models.DateTimeField(_("Date created"), blank=True, null=True, auto_now_add=True, editable=False) - date_updated = models.DateTimeField(_("Date updated"), blank=True, null=True, auto_now=True, editable=False) + date_published = models.DateTimeField(_("Date published"), blank=True, + null=True, + default=datetime.datetime.now()) + date_created = models.DateTimeField(_("Date created"), blank=True, + null=True, auto_now_add=True, + editable=False) + date_updated = models.DateTimeField(_("Date updated"), blank=True, + null=True, auto_now=True, + editable=False) class Meta: + """Meta.""" + abstract = True verbose_name = _("Foo item") verbose_name_plural = _("Foo items") - def __unicode__(self): + def __str__(self): return self.title + class FooItem(FooItemBase): + """FooItem.""" + class Meta: + """Meta.""" + verbose_name = _("Foo item") verbose_name_plural = _("Foo items") + class Foo2Item(FooItemBase): + """Foo2Item.""" + class Meta: + """Meta.""" + verbose_name = _("Foo 2 item") verbose_name_plural = _("Foo 2 items") + class Foo3Item(FooItemBase): + """Foo3Item.""" + class Meta: + """Meta.""" + verbose_name = _("Foo 3 item") verbose_name_plural = _("Foo 3 items") + class Foo4Item(FooItemBase): + """Foo4Item.""" + class Meta: + """Meta.""" + verbose_name = _("Foo 4 item") verbose_name_plural = _("Foo 4 items") diff --git a/example/example/manage.py b/examples/simple/manage.py similarity index 100% rename from example/example/manage.py rename to examples/simple/manage.py diff --git a/examples/simple/settings/__init__.py b/examples/simple/settings/__init__.py new file mode 100644 index 0000000..c787328 --- /dev/null +++ b/examples/simple/settings/__init__.py @@ -0,0 +1 @@ +from .dev import * diff --git a/examples/simple/settings/base.py b/examples/simple/settings/base.py new file mode 100644 index 0000000..ef84062 --- /dev/null +++ b/examples/simple/settings/base.py @@ -0,0 +1,339 @@ +# Django settings for example project. +import os +import sys + +from nine import versions + +from .core import PROJECT_DIR, gettext + + +DEBUG = False +DEBUG_TOOLBAR = False +TEMPLATE_DEBUG = DEBUG +DEV = False + +ADMINS = ( + # ('Your Name', 'your_email@example.com'), +) + +MANAGERS = ADMINS + +DATABASES = { + 'default': { + # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. + 'ENGINE': 'django.db.backends.sqlite3', + # Or path to database file if using sqlite3. + 'NAME': PROJECT_DIR(os.path.join('..', '..', 'db', 'example.db')), + # The following settings are not used with sqlite3: + 'USER': '', + 'PASSWORD': '', + # Empty for localhost through domain sockets or '127.0.0.1' for + # localhost through TCP. + 'HOST': '', + # Set to empty string for default. + 'PORT': '', + } +} + +# Hosts/domain names that are valid for this site; required if DEBUG is False +# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts +ALLOWED_HOSTS = [] + +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be available on all operating systems. +# In a Windows environment this must be set to your system time zone. +TIME_ZONE = 'America/Chicago' + +# Language code for this installation. All choices can be found here: +# http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = 'en-us' + +SITE_ID = 1 + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = True + +# If you set this to False, Django will not format dates, numbers and +# calendars according to the current locale. +USE_L10N = True + +# If you set this to False, Django will not use timezone-aware datetimes. +USE_TZ = True + +# Absolute filesystem path to the directory that will hold user-uploaded files. +# Example: "/var/www/example.com/media/" +MEDIA_ROOT = PROJECT_DIR(os.path.join('..', '..', 'media')) + +# URL that handles the media served from MEDIA_ROOT. Make sure to use a +# trailing slash. +# Examples: "http://example.com/media/", "http://media.example.com/" +MEDIA_URL = '/media/' + +# Absolute path to the directory static files should be collected to. +# Don't put anything in this directory yourself; store your static files +# in apps' "static/" subdirectories and in STATICFILES_DIRS. +# Example: "/var/www/example.com/static/" +STATIC_ROOT = PROJECT_DIR(os.path.join('..', '..', 'static')) + +# URL prefix for static files. +# Example: "http://example.com/static/", "http://static.example.com/" +STATIC_URL = '/static/' + +# Additional locations of static files +STATICFILES_DIRS = ( + # Put strings here, like "/home/html/static" or "C:/www/django/static". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. + PROJECT_DIR(os.path.join('..', '..', 'media', 'static')), +) + +# List of finder classes that know how to find static files in +# various locations. +STATICFILES_FINDERS = ( + 'django.contrib.staticfiles.finders.FileSystemFinder', + 'django.contrib.staticfiles.finders.AppDirectoriesFinder', + # 'django.contrib.staticfiles.finders.DefaultStorageFinder', +) + +# Make this unique, and don't share it with anybody. +SECRET_KEY = '6sf18c*w971i8a-m^1coasrmur2k6+q5_kyn*)s@(*_dk5q3&r' + + +try: + from .local_settings import DEBUG_TEMPLATE +except Exception as err: + DEBUG_TEMPLATE = False + + +if versions.DJANGO_GTE_1_10: + TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + # 'APP_DIRS': True, + 'DIRS': [PROJECT_DIR(os.path.join('..', 'templates'))], + 'OPTIONS': { + 'context_processors': [ + "django.template.context_processors.debug", + 'django.template.context_processors.request', + "django.contrib.auth.context_processors.auth", + # "django.core.context_processors.i18n", + # "django.core.context_processors.media", + # "django.core.context_processors.static", + # "django.core.context_processors.tz", + "django.contrib.messages.context_processors.messages", + ], + 'loaders': [ + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', + 'django.template.loaders.eggs.Loader', + ], + 'debug': DEBUG_TEMPLATE, + } + }, + ] +elif versions.DJANGO_GTE_1_8: + TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + # 'APP_DIRS': True, + 'DIRS': [PROJECT_DIR(os.path.join('..', 'templates'))], + 'OPTIONS': { + 'context_processors': [ + "django.contrib.auth.context_processors.auth", + "django.template.context_processors.debug", + "django.template.context_processors.i18n", + "django.template.context_processors.media", + "django.template.context_processors.static", + "django.template.context_processors.tz", + "django.contrib.messages.context_processors.messages", + "django.template.context_processors.request", + ], + 'loaders': [ + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', + 'django.template.loaders.eggs.Loader', + ], + 'debug': DEBUG_TEMPLATE, + } + }, + ] +else: + TEMPLATE_DEBUG = DEBUG_TEMPLATE + + # List of callables that know how to import templates from various + # sources. + TEMPLATE_LOADERS = [ + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', + 'django.template.loaders.eggs.Loader', + + ] + + TEMPLATE_CONTEXT_PROCESSORS = ( + "django.contrib.auth.context_processors.auth", + "django.core.context_processors.debug", + "django.core.context_processors.i18n", + "django.core.context_processors.media", + "django.core.context_processors.static", + "django.core.context_processors.tz", + "django.contrib.messages.context_processors.messages", + "django.core.context_processors.request", + ) + + TEMPLATE_DIRS = ( + # Put strings here, like "/home/html/django_templates" or + # "C:/www/django/templates". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. + PROJECT_DIR(os.path.join('..', 'templates')), + ) + +# # List of callables that know how to import templates from various sources. +# TEMPLATE_LOADERS = ( +# 'django.template.loaders.filesystem.Loader', +# 'django.template.loaders.app_directories.Loader', +# 'django.template.loaders.eggs.Loader', +# ) + +MIDDLEWARE_CLASSES = ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + # Uncomment the next line for simple clickjacking protection: + # 'django.middleware.clickjacking.XFrameOptionsMiddleware', +) + +ROOT_URLCONF = 'urls' + +# Python dotted path to the WSGI application used by Django's runserver. +WSGI_APPLICATION = 'wsgi.application' + +# TEMPLATE_DIRS = ( +# # Put strings here, like "/home/html/django_templates" or +# # "C:/www/django/templates". +# # Always use forward slashes, even on Windows. +# # Don't forget to use absolute paths, not relative paths. +# PROJECT_DIR('templates'), +# ) + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.messages', + 'django.contrib.staticfiles', + # Uncomment the next line to enable the admin: + 'django.contrib.admin', + # Uncomment the next line to enable admin documentation: + # 'django.contrib.admindocs', + + 'admin_timeline', # admin-timeline + + 'foo', # Example app to generate test data +) + +# A sample logging configuration. The only tangible logging +# performed by this configuration is to send an email to +# the site admins on every HTTP 500 error when DEBUG=False. +# See http://docs.djangoproject.com/en/dev/topics/logging for +# more details on how to customize your logging configuration. +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'filters': { + 'require_debug_false': { + '()': 'django.utils.log.RequireDebugFalse' + } + }, + 'formatters': { + 'verbose': { + 'format': '\n%(levelname)s %(asctime)s [%(pathname)s:%(lineno)s] ' + '%(message)s' + }, + 'simple': { + 'format': '\n%(levelname)s %(message)s' + }, + }, + 'handlers': { + 'mail_admins': { + 'level': 'ERROR', + 'filters': ['require_debug_false'], + 'class': 'django.utils.log.AdminEmailHandler' + }, + 'console': { + 'level': 'DEBUG', + 'class': 'logging.StreamHandler', + 'formatter': 'verbose' + }, + 'django_log': { + 'level': 'DEBUG', + 'class': 'logging.handlers.RotatingFileHandler', + 'filename': PROJECT_DIR( + os.path.join('..', '..', 'logs', 'django.log') + ), + 'maxBytes': 1048576, + 'backupCount': 99, + 'formatter': 'verbose', + }, + 'admin_timeline_log': { + 'level': 'DEBUG', + 'class': 'logging.handlers.RotatingFileHandler', + 'filename': PROJECT_DIR( + os.path.join('..', '..', 'logs', 'admin_timeline.log') + ), + 'maxBytes': 1048576, + 'backupCount': 99, + 'formatter': 'verbose', + }, + }, + 'loggers': { + 'django.request': { + 'handlers': ['mail_admins'], + 'level': 'ERROR', + 'propagate': True, + }, + 'django': { + 'handlers': ['django_log'], + 'level': 'ERROR', + 'propagate': False, + }, + 'admin_timeline': { + 'handlers': ['console', 'admin_timeline_log'], + 'level': 'DEBUG', + 'propagate': True, + }, + } +} + + +# Do not put any settings below this line +try: + from .local_settings import * +except: + pass + +# Make the `django-admin-timeline` package available without installation. +if DEV: + admin_timeline_source_path = os.environ.get( + 'ADMIN_TIMELINE_SOURCE_PATH', 'src' + ) + sys.path.insert(0, os.path.abspath(admin_timeline_source_path)) + +if DEBUG and DEBUG_TOOLBAR: + # debug_toolbar + MIDDLEWARE_CLASSES += ( + 'debug_toolbar.middleware.DebugToolbarMiddleware', + ) + + INSTALLED_APPS += ( + 'debug_toolbar', + ) + + DEBUG_TOOLBAR_CONFIG = { + 'INTERCEPT_REDIRECTS': False, + } diff --git a/examples/simple/settings/core.py b/examples/simple/settings/core.py new file mode 100644 index 0000000..a48b811 --- /dev/null +++ b/examples/simple/settings/core.py @@ -0,0 +1,14 @@ +import os + + +def project_dir(base): + return os.path.abspath( + os.path.join(os.path.dirname(__file__), base).replace('\\', '/') + ) + + +PROJECT_DIR = project_dir + + +def gettext(val): + return val diff --git a/examples/simple/settings/dev.py b/examples/simple/settings/dev.py new file mode 100644 index 0000000..9b5ed21 --- /dev/null +++ b/examples/simple/settings/dev.py @@ -0,0 +1 @@ +from .base import * diff --git a/example/example/settings_django_1_7.py b/examples/simple/settings/django_1_7.py similarity index 95% rename from example/example/settings_django_1_7.py rename to examples/simple/settings/django_1_7.py index 90e5f5e..38fa417 100644 --- a/example/example/settings_django_1_7.py +++ b/examples/simple/settings/django_1_7.py @@ -1,4 +1,4 @@ -from settings import * +from .base import * INSTALLED_APPS = list(INSTALLED_APPS) diff --git a/example/example/settings_django_1_8.py b/examples/simple/settings/django_1_8.py similarity index 95% rename from example/example/settings_django_1_8.py rename to examples/simple/settings/django_1_8.py index 90e5f5e..38fa417 100644 --- a/example/example/settings_django_1_8.py +++ b/examples/simple/settings/django_1_8.py @@ -1,4 +1,4 @@ -from settings import * +from .base import * INSTALLED_APPS = list(INSTALLED_APPS) diff --git a/examples/simple/settings/docs.py b/examples/simple/settings/docs.py new file mode 100644 index 0000000..9b5ed21 --- /dev/null +++ b/examples/simple/settings/docs.py @@ -0,0 +1 @@ +from .base import * diff --git a/examples/simple/settings/local_settings.example b/examples/simple/settings/local_settings.example new file mode 100644 index 0000000..cad5db4 --- /dev/null +++ b/examples/simple/settings/local_settings.example @@ -0,0 +1,44 @@ +# Django settings for resato_portal project. +import os + +from .core import PROJECT_DIR + +DEBUG = True +DEBUG_TOOLBAR = not True +# TEMPLATE_DEBUG = DEBUG +DEBUG_TEMPLATE = DEBUG +DEV = False + +DATABASES = { + 'default': { + # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. + 'ENGINE': 'django.db.backends.sqlite3', + # Or path to database file if using sqlite3. + 'NAME': PROJECT_DIR(os.path.join('..', '..', 'db', 'example.db')), + # The following settings are not used with sqlite3: + 'USER': '', + 'PASSWORD': '', + # Empty for localhost through domain sockets or '127.0.0.1' for + # localhost through TCP. + 'HOST': '', + # Set to empty string for default. + 'PORT': '', + } +} + +INTERNAL_IPS = ('127.0.0.1',) + +EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend' +EMAIL_FILE_PATH = PROJECT_DIR(os.path.join('..', '..', 'tmp')) + +DEFAULT_FROM_EMAIL = '' + +ADMIN_TIMELINE_NUMBER_OF_ENTRIES_PER_PAGE = 4 + +os.environ.setdefault( + 'ADMIN_TIMELINE_SOURCE_PATH', + '/home/user/repos/django_admin_timeline/src' +) + +FIREFOX_BIN_PATH = '/usr/lib/firefox47/firefox' +# PHANTOM_JS_EXECUTABLE_PATH = '' diff --git a/examples/simple/settings/local_settings.py b/examples/simple/settings/local_settings.py new file mode 100644 index 0000000..e9b43f4 --- /dev/null +++ b/examples/simple/settings/local_settings.py @@ -0,0 +1,44 @@ +# Django settings for resato_portal project. +import os + +from .core import PROJECT_DIR + +DEBUG = True +DEBUG_TOOLBAR = not True +# TEMPLATE_DEBUG = DEBUG +DEBUG_TEMPLATE = DEBUG +DEV = True + +DATABASES = { + 'default': { + # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. + 'ENGINE': 'django.db.backends.sqlite3', + # Or path to database file if using sqlite3. + 'NAME': PROJECT_DIR(os.path.join('..', '..', 'db', 'example.db')), + # The following settings are not used with sqlite3: + 'USER': '', + 'PASSWORD': '', + # Empty for localhost through domain sockets or '127.0.0.1' for + # localhost through TCP. + 'HOST': '', + # Set to empty string for default. + 'PORT': '', + } +} + +INTERNAL_IPS = ('127.0.0.1',) + +EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend' +EMAIL_FILE_PATH = PROJECT_DIR(os.path.join('..', '..', 'tmp')) + +DEFAULT_FROM_EMAIL = '' + +ADMIN_TIMELINE_NUMBER_OF_ENTRIES_PER_PAGE = 4 + +os.environ.setdefault( + 'ADMIN_TIMELINE_SOURCE_PATH', + '/home/foreverchild/bbrepos/django_admin_timeline/src' +) + +FIREFOX_BIN_PATH = '/usr/lib/firefox47/firefox' +PHANTOM_JS_EXECUTABLE_PATH = '' diff --git a/examples/simple/settings/testing.py b/examples/simple/settings/testing.py new file mode 100644 index 0000000..9b5ed21 --- /dev/null +++ b/examples/simple/settings/testing.py @@ -0,0 +1 @@ +from .base import * diff --git a/example/example/urls.py b/examples/simple/urls.py similarity index 65% rename from example/example/urls.py rename to examples/simple/urls.py index f966fc2..41588da 100644 --- a/example/example/urls.py +++ b/examples/simple/urls.py @@ -1,19 +1,20 @@ from django.conf import settings -from django.conf.urls import patterns, include, url +from django.conf.urls import include, url from django.contrib.staticfiles.urls import staticfiles_urlpatterns from django.conf.urls.static import static from django.contrib import admin admin.autodiscover() -urlpatterns = patterns('', +urlpatterns = [ # django-admin-timeline URLs. Should come before the django-admin URLs. - (r'^admin/timeline/', include('admin_timeline.urls')), + url(r'^admin/timeline/', include('admin_timeline.urls')), # Uncomment the next line to enable the admin: url(r'^admin/', include(admin.site.urls)), -) +] if settings.DEBUG: urlpatterns += staticfiles_urlpatterns() - urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + urlpatterns += static(settings.MEDIA_URL, + document_root=settings.MEDIA_ROOT) diff --git a/example/example/wsgi.py b/examples/simple/wsgi.py similarity index 100% rename from example/example/wsgi.py rename to examples/simple/wsgi.py diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..56cceac --- /dev/null +++ b/pytest.ini @@ -0,0 +1,21 @@ +[pytest] +norecursedirs= + *.egg + .hg + .git + .tox + .env + _sass + build + dist + migrations + releases +python_files = + test_*.py + tests.py +DJANGO_SETTINGS_MODULE=settings.testing +addopts= + --cov=admin_timeline + --ignore=.tox + --ignore=requirements + --ignore=releases diff --git a/runtests.py b/runtests.py new file mode 100755 index 0000000..24e49d5 --- /dev/null +++ b/runtests.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +import os +import sys +import pytest + + +def main(): + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings.testing") + sys.path.insert(0, os.path.abspath('src')) + sys.path.insert(0, "examples/simple") + return pytest.main() + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/scripts/build_docs.sh b/scripts/build_docs.sh index fef5e27..fecc8cd 100755 --- a/scripts/build_docs.sh +++ b/scripts/build_docs.sh @@ -1,8 +1,8 @@ -pip install -r example/requirements.txt +#pip install -r example/requirements.txt rm builddocs.zip rm builddocs -rf -./scripts/uninstall.sh -./scripts/install.sh +#./scripts/uninstall.sh +#./scripts/install.sh cat README.rst SCREENSHOTS.rst docs/documentation.rst.distrib > docs/index.rst sphinx-build -n -a -b html docs builddocs -cd builddocs && zip -r ../builddocs.zip . -x ".*" && cd .. \ No newline at end of file +cd builddocs && zip -r ../builddocs.zip . -x ".*" && cd .. diff --git a/scripts/clean_up.sh b/scripts/clean_up.sh index 5cdce15..625d9a0 100755 --- a/scripts/clean_up.sh +++ b/scripts/clean_up.sh @@ -1,3 +1,5 @@ find . -name "*.pyc" -exec rm -rf {} \; +find . -name "__pycache__" -exec rm -rf {} \; rm -rf build/ -rm -rf dist/ \ No newline at end of file +rm -rf dist/ +rm -rf .cache/ diff --git a/scripts/compile_messages.sh b/scripts/compile_messages.sh index b430912..23e521f 100755 --- a/scripts/compile_messages.sh +++ b/scripts/compile_messages.sh @@ -6,8 +6,8 @@ cd src/admin_timeline/ #django-admin.py compilemessages -l ru echo 'Compiling messages for example projects...' -cd ../../example/example/ +cd ../../examples/simple/ #django-admin.py compilemessages -l hy #django-admin.py compilemessages -l de #django-admin.py compilemessages -l nl -#django-admin.py compilemessages -l ru \ No newline at end of file +#django-admin.py compilemessages -l ru diff --git a/scripts/install.sh b/scripts/install.sh index bde5b01..87f9289 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1,4 +1,4 @@ -mkdir -p example/media/ example/static/ example/db/ +mkdir -p examples/media/ examples/static/ example/db/ python setup.py install -./example/example/manage.py collectstatic --noinput -./example/example/manage.py syncdb --noinput +./examples/simple/manage.py collectstatic --noinput +./examples/simple/manage.py migrate --noinput diff --git a/scripts/install_django_1_4.sh b/scripts/install_django_1_4.sh index 0d52417..6ca9e54 100755 --- a/scripts/install_django_1_4.sh +++ b/scripts/install_django_1_4.sh @@ -1,5 +1,5 @@ -pip install -r example/requirements_django_1_4.txt -mkdir -p example/media/ example/static/ example/db/ +pip install -r examples/requirements/django_1_4.txt +mkdir -p examples/media/ examples/static/ examples/db/ python setup.py install -./example/example/manage.py collectstatic --noinput -./example/example/manage.py syncdb --noinput +./examples/simple/manage.py collectstatic --noinput +./examples/simple/manage.py migrate --noinput diff --git a/scripts/install_django_1_5.sh b/scripts/install_django_1_5.sh index c546b7a..3041f68 100755 --- a/scripts/install_django_1_5.sh +++ b/scripts/install_django_1_5.sh @@ -1,5 +1,5 @@ -pip install -r example/requirements_django_1_5.txt -mkdir -p example/media/ example/static/ example/db/ +pip install -r examples/requirements/django_1_5.txt +mkdir -p examples/media/ examples/static/ examples/db/ python setup.py install -./example/example/manage.py collectstatic --noinput -./example/example/manage.py syncdb --noinput +./examples/simple/manage.py collectstatic --noinput +./examples/simple/manage.py migrate --noinput diff --git a/scripts/install_django_1_6.sh b/scripts/install_django_1_6.sh index fc69850..6275820 100755 --- a/scripts/install_django_1_6.sh +++ b/scripts/install_django_1_6.sh @@ -1,7 +1,5 @@ -#pip install -r example/requirements.txt --allow-all-external --allow-unverified django-admin-tools -#cd .. -pip install -r example/requirements_django_1_6.txt -mkdir -p example/media/ example/static/ example/db/ +pip install -r examples/requirements/django_1_6.txt +mkdir -p examples/media/ examples/static/ examples/db/ python setup.py install -./example/example/manage.py collectstatic --noinput -./example/example/manage.py syncdb --noinput +./examples/simple/manage.py collectstatic --noinput +./examples/simple/manage.py migrate --noinput diff --git a/scripts/install_django_1_7.sh b/scripts/install_django_1_7.sh index 9256ca9..f8d433d 100755 --- a/scripts/install_django_1_7.sh +++ b/scripts/install_django_1_7.sh @@ -1,7 +1,5 @@ -#pip install -r example/requirements.txt --allow-all-external --allow-unverified django-admin-tools -#cd .. -pip install -r example/requirements_django_1_7.txt -mkdir -p example/media/ example/static/ example/db/ +pip install -r examples/requirements/django_1_7.txt +mkdir -p examples/media/ examples/static/ examples/db/ python setup.py install -./example/example/manage.py collectstatic --noinput --settings=settings_django_1_7 --traceback -v 3 -./example/example/manage.py syncdb --noinput --settings=settings_django_1_7 --traceback -v 3 +./examples/simple/manage.py collectstatic --noinput --settings=settings_django_1_7 --traceback -v 3 +./examples/simple/manage.py migrate --noinput --settings=settings_django_1_7 --traceback -v 3 diff --git a/scripts/install_django_1_8.sh b/scripts/install_django_1_8.sh index 23bb336..56783f6 100755 --- a/scripts/install_django_1_8.sh +++ b/scripts/install_django_1_8.sh @@ -1,7 +1,5 @@ -#pip install -r example/requirements.txt --allow-all-external --allow-unverified django-admin-tools -#cd .. -pip install -r example/requirements_django_1_8.txt -mkdir -p example/media/ example/static/ example/db/ +pip install -r examples/requirements/django_1_8.txt +mkdir -p examples/media/ examples/static/ examples/db/ python setup.py install -./example/example/manage.py collectstatic --noinput --settings=settings_django_1_8 --traceback -v 3 -./example/example/manage.py syncdb --noinput --settings=settings_django_1_8 --traceback -v 3 +./examples/simple/manage.py collectstatic --noinput --settings=settings_django_1_8 --traceback -v 3 +./examples/simple/manage.py migrate --noinput --settings=settings_django_1_8 --traceback -v 3 diff --git a/scripts/make_release.sh b/scripts/make_release.sh index 30c5bed..15ab952 100755 --- a/scripts/make_release.sh +++ b/scripts/make_release.sh @@ -1,2 +1,2 @@ python setup.py register -python setup.py sdist bdist_wheel upload \ No newline at end of file +python setup.py sdist bdist_wheel upload diff --git a/scripts/prepare_docs.sh b/scripts/prepare_docs.sh new file mode 100755 index 0000000..92975c0 --- /dev/null +++ b/scripts/prepare_docs.sh @@ -0,0 +1 @@ +cat README.rst SCREENSHOTS.rst docs/documentation.rst.distrib > docs/index.rst diff --git a/scripts/pycodestyle.sh b/scripts/pycodestyle.sh new file mode 100755 index 0000000..853829a --- /dev/null +++ b/scripts/pycodestyle.sh @@ -0,0 +1,2 @@ +reset +pycodestyle src/admin_timeline/ --exclude src/admin_timeline/migrations/,src/admin_timeline/south_migrations/ diff --git a/scripts/pycodestyle_example.sh b/scripts/pycodestyle_example.sh new file mode 100755 index 0000000..28125c5 --- /dev/null +++ b/scripts/pycodestyle_example.sh @@ -0,0 +1,2 @@ +reset +pycodestyle examples/simple/ --exclude examples/simple/page/migrations/,examples/simple/page/south_migrations/,examples/simple/wsgi.py diff --git a/scripts/pylint.sh b/scripts/pylint.sh new file mode 100755 index 0000000..09dda3f --- /dev/null +++ b/scripts/pylint.sh @@ -0,0 +1,2 @@ +reset +pylint src/admin_timeline/ diff --git a/setup.py b/setup.py index 725f06a..3a930aa 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,6 @@ readme = readme.replace('.. code-block:: none', '.. code-block::') screenshots = open(os.path.join(os.path.dirname(__file__), 'SCREENSHOTS.rst')).read() screenshots = screenshots.replace('.. image:: _static', '.. figure:: https://github.com/barseghyanartur/django-admin-timeline/raw/master/docs/_static') - screenshots = screenshots.replace('.. code-block:: none', '.. code-block::') except: readme = '' screenshots = '' @@ -17,43 +16,49 @@ static_dir = "src/admin_timeline/static" static_files = [os.path.join(static_dir, f) for f in os.listdir(static_dir)] -version = '1.5.4' +version = '1.6' setup( - name = 'django-admin-timeline', - version = version, - description = ("Facebook-like timeline for Django admin"), - long_description = "{0}{1}".format(readme, screenshots), - classifiers = [ + name='django-admin-timeline', + version=version, + description="Facebook-like timeline for Django admin", + long_description="{0}{1}".format(readme, screenshots), + classifiers=[ "Programming Language :: Python", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", "Environment :: Web Environment", "Framework :: Django", "Intended Audience :: Developers", "Operating System :: OS Independent", "Development Status :: 5 - Production/Stable", "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", - "License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)", + "License :: OSI Approved :: GNU Lesser General Public License v2 or " + "later (LGPLv2+)", ], - keywords = 'django-admin-timeline, django, app, python', - author = 'Artur Barseghyan', - author_email = 'artur.barseghyan@gmail.com', - url = 'https://github.com/barseghyanartur/django-admin-timeline', - package_dir = {'':'src'}, - packages = find_packages(where='./src'), - license = 'GPL 2.0/LGPL 2.1', - package_data = { + keywords='django-admin-timeline, django, app, python', + author='Artur Barseghyan', + author_email='artur.barseghyan@gmail.com', + url='https://github.com/barseghyanartur/django-admin-timeline', + package_dir={'': 'src'}, + packages=find_packages(where='./src'), + license='GPL 2.0/LGPL 2.1', + package_data={ 'admin_timeline': templates + static_files }, - include_package_data = True, - install_requires = [ + include_package_data=True, + install_requires=[ + 'six>=1.9', 'django>=1.4', - 'django-nine>=0.1.4' + 'django-nine>=0.1.10' ], - tests_require = [ + tests_require=[ + 'factory_boy', + 'fake-factory', + 'pytest', + 'pytest-django', 'radar>=0.3', ], ) diff --git a/src/admin_timeline/__init__.py b/src/admin_timeline/__init__.py index 7adbee8..f8f74ef 100644 --- a/src/admin_timeline/__init__.py +++ b/src/admin_timeline/__init__.py @@ -1,6 +1,6 @@ __title__ = 'django-admin-timeline' -__version__ = '1.5.4' -__build__ = 0x000013 +__version__ = '1.6' +__build__ = 0x000014 __author__ = 'Artur Barseghyan ' -__copyright__ = 'Copyright (c) 2013-2015 Artur Barseghyan' +__copyright__ = '2013-2016 Artur Barseghyan' __license__ = 'GPL 2.0/LGPL 2.1' diff --git a/src/admin_timeline/compat.py b/src/admin_timeline/compat.py index a2fbfaa..08eab46 100644 --- a/src/admin_timeline/compat.py +++ b/src/admin_timeline/compat.py @@ -1,10 +1,14 @@ +from nine.versions import DJANGO_LTE_1_4 + __title__ = 'admin_timeline.compat' __author__ = 'Artur Barseghyan ' -__copyright__ = 'Copyright (c) 2013-2015 Artur Barseghyan' +__copyright__ = '2013-2016 Artur Barseghyan' __license__ = 'GPL 2.0/LGPL 2.1' -__all__ = ('TEMPLATE_NAME', 'TEMPLATE_NAME_AJAX',) +__all__ = ( + 'TEMPLATE_NAME', + 'TEMPLATE_NAME_AJAX', +) -from nine.versions import DJANGO_LTE_1_4 TEMPLATE_NAME = "admin_timeline/timeline.html" TEMPLATE_NAME_AJAX = "admin_timeline/timeline_ajax.html" diff --git a/src/admin_timeline/conf.py b/src/admin_timeline/conf.py index 69b445a..051649f 100644 --- a/src/admin_timeline/conf.py +++ b/src/admin_timeline/conf.py @@ -1,19 +1,19 @@ +from django.conf import settings + +from . import defaults + __title__ = 'admin_timeline.conf' __author__ = 'Artur Barseghyan ' -__copyright__ = 'Copyright (c) 2013-2015 Artur Barseghyan' +__copyright__ = '2013-2016 Artur Barseghyan' __license__ = 'GPL 2.0/LGPL 2.1' __all__ = ('get_setting',) -from django.conf import settings - -from admin_timeline import defaults def get_setting(setting, override=None): - """ - Get a setting from ``admin_timeline`` conf module, falling back to the - default. + """Get a setting from ``admin_timeline`` conf module. - If override is not None, it will be used instead of the setting. + Falling back to the default. If override is not None, it will be used + instead of the setting. :param setting: String with setting name :param override: Value to use when no default setting is available. diff --git a/src/admin_timeline/defaults.py b/src/admin_timeline/defaults.py index 35eda06..9a0fb3f 100644 --- a/src/admin_timeline/defaults.py +++ b/src/admin_timeline/defaults.py @@ -1,10 +1,12 @@ __title__ = 'admin_timeline.defaults' __author__ = 'Artur Barseghyan ' -__copyright__ = 'Copyright (c) 2013-2015 Artur Barseghyan' +__copyright__ = '2013-2016 Artur Barseghyan' __license__ = 'GPL 2.0/LGPL 2.1' __all__ = ( - 'NUMBER_OF_ENTRIES_PER_PAGE', 'LOG_ENTRIES_DAY_HEADINGS_DATE_FORMAT', - 'SINGLE_LOG_ENTRY_DATE_FORMAT', 'DEBUG' + 'NUMBER_OF_ENTRIES_PER_PAGE', + 'LOG_ENTRIES_DAY_HEADINGS_DATE_FORMAT', + 'SINGLE_LOG_ENTRY_DATE_FORMAT', + 'DEBUG' ) # Number of entries per page. Used in both non-AJAX and AJAX driven views. @@ -14,7 +16,7 @@ LOG_ENTRIES_DAY_HEADINGS_DATE_FORMAT = "l j F Y" # Single log entry date format in timeline. -SINGLE_LOG_ENTRY_DATE_FORMAT = "g:i:s A" +SINGLE_LOG_ENTRY_DATE_FORMAT = "g:i:s A" # Personal debug mode, which has nothing to do with global settings.DEBUG DEBUG = False diff --git a/src/admin_timeline/forms.py b/src/admin_timeline/forms.py index 4e3abb6..dbb277a 100644 --- a/src/admin_timeline/forms.py +++ b/src/admin_timeline/forms.py @@ -1,15 +1,39 @@ +from django import forms +from django.contrib.admin.models import LogEntry + __title__ = 'admin_timeline.forms' __author__ = 'Artur Barseghyan ' -__copyright__ = 'Copyright (c) 2013-2015 Artur Barseghyan' +__copyright__ = '2013-2016 Artur Barseghyan' __license__ = 'GPL 2.0/LGPL 2.1' -__all__ = ('FilterForm',) +__all__ = ( + 'get_users', + 'get_content_types', + 'FilterForm', +) + + +def get_users(data): + """Get user choices.""" + return list( + set( + [ + ( + d.user_id, d.user.get_full_name() + if d.user.get_full_name() + else d.user.username + ) for d in data + ] + ) + ) + + +def get_content_types(data): + """Get content type choices.""" + return list(set([(d.content_type_id, d.content_type.name) for d in data])) -from django import forms -from django.contrib.admin.models import LogEntry class FilterForm(forms.Form): - """ - Filter form to be used in the timeline. + """Filter form to be used in the timeline. ``users``: Users list to be filtered on. @@ -22,19 +46,15 @@ def __init__(self, *args, **kwargs): .select_related('user', 'content_type') \ .only('content_type', 'user')[:] - users = lambda data: list(set([(d.user_id, d.user.get_full_name() if d.user.get_full_name() else d.user.username) \ - for d in data])) - content_types = lambda data: list(set([(d.content_type_id, d.content_type.name) for d in data])) - super(FilterForm, self).__init__(*args, **kwargs) self.fields['users'] = forms.MultipleChoiceField( - choices = users(data), - #widget = forms.CheckboxSelectMultiple, - required = False - ) + choices=get_users(data), + # widget=forms.CheckboxSelectMultiple, + required=False + ) self.fields['content_types'] = forms.MultipleChoiceField( - choices = content_types(data), - #widget = forms.CheckboxSelectMultiple, - required = False - ) + choices=get_content_types(data), + # widget=forms.CheckboxSelectMultiple, + required=False + ) diff --git a/src/admin_timeline/settings.py b/src/admin_timeline/settings.py index 148ba25..08bd5af 100644 --- a/src/admin_timeline/settings.py +++ b/src/admin_timeline/settings.py @@ -15,21 +15,23 @@ ``DEBUG`` """ +from admin_timeline.conf import get_setting + __title__ = 'admin_timeline.settings' __author__ = 'Artur Barseghyan ' -__copyright__ = 'Copyright (c) 2013-2015 Artur Barseghyan' +__copyright__ = '2013-2016 Artur Barseghyan' __license__ = 'GPL 2.0/LGPL 2.1' __all__ = ( - 'NUMBER_OF_ENTRIES_PER_PAGE', 'SINGLE_LOG_ENTRY_DATE_FORMAT', - 'LOG_ENTRIES_DAY_HEADINGS_DATE_FORMAT', 'DEBUG', + 'NUMBER_OF_ENTRIES_PER_PAGE', + 'SINGLE_LOG_ENTRY_DATE_FORMAT', + 'LOG_ENTRIES_DAY_HEADINGS_DATE_FORMAT', + 'DEBUG', ) -from admin_timeline.conf import get_setting - NUMBER_OF_ENTRIES_PER_PAGE = get_setting('NUMBER_OF_ENTRIES_PER_PAGE') SINGLE_LOG_ENTRY_DATE_FORMAT = get_setting('SINGLE_LOG_ENTRY_DATE_FORMAT') LOG_ENTRIES_DAY_HEADINGS_DATE_FORMAT = get_setting( 'LOG_ENTRIES_DAY_HEADINGS_DATE_FORMAT' - ) +) DEBUG = get_setting('DEBUG') diff --git a/src/admin_timeline/templatetags/admin_timeline_tags.py b/src/admin_timeline/templatetags/admin_timeline_tags.py index 2c56c07..34a0651 100644 --- a/src/admin_timeline/templatetags/admin_timeline_tags.py +++ b/src/admin_timeline/templatetags/admin_timeline_tags.py @@ -1,9 +1,3 @@ -__title__ = 'admin_timeline.templatetags.admin_timeline_tags' -__author__ = 'Artur Barseghyan ' -__copyright__ = 'Copyright (c) 2013-2015 Artur Barseghyan' -__license__ = 'GPL 2.0/LGPL 2.1' -__all__ = ('assign', 'get_full_name', 'resolve_admin_url',) - from django import template from django.core.urlresolvers import reverse, NoReverseMatch @@ -14,24 +8,37 @@ else: from django.contrib.admin.util import quote +__title__ = 'admin_timeline.templatetags.admin_timeline_tags' +__author__ = 'Artur Barseghyan ' +__copyright__ = '2013-2016 Artur Barseghyan' +__license__ = 'GPL 2.0/LGPL 2.1' +__all__ = ( + 'assign', + 'get_full_name', + 'resolve_admin_url', + 'AssignNode', + 'GetFullNameNode', +) + register = template.Library() + class AssignNode(template.Node): - """ - Node for ``assign`` tag. - """ + """Node for ``assign`` tag.""" + def __init__(self, value, as_var): self.value = value self.as_var = as_var def render(self, context): + """Render.""" context[self.as_var] = self.value.resolve(context, True) return '' + @register.tag def assign(parser, token): - """ - Assign an expression to a variable in the current context. + """Assign an expression to a variable in the current context. Syntax:: {% assign [value] as [name] %} @@ -47,22 +54,22 @@ def assign(parser, token): class GetFullNameNode(template.Node): - """ - Node for ``get_full_name`` tag. - """ + """Node for ``get_full_name`` tag.""" + def __init__(self, user, as_var): self.user = user self.as_var = as_var def render(self, context): + """Render.""" user = self.user.resolve(context, True) context[self.as_var] = user.get_full_name() or user.username return '' + @register.tag def get_full_name(parser, token): - """ - Get users' full name. + """Get users' full name. Syntax:: {% get_full_name [user] as [name] %} @@ -76,8 +83,10 @@ def get_full_name(parser, token): user = parser.compile_filter(bits[1]) return GetFullNameNode(user=user, as_var=bits[-1]) + @register.filter def resolve_admin_url(entry): + """Resolve admin URL.""" if entry.content_type and entry.object_id: url_name = 'admin:%s_%s_change' % (entry.content_type.app_label, entry.content_type.model) diff --git a/src/admin_timeline/tests.py b/src/admin_timeline/tests.py deleted file mode 100644 index a7808a0..0000000 --- a/src/admin_timeline/tests.py +++ /dev/null @@ -1,441 +0,0 @@ -from __future__ import print_function - -__title__ = 'admin_timeline.tests' -__author__ = 'Artur Barseghyan ' -__copyright__ = 'Copyright (c) 2013-2014 Artur Barseghyan' -__license__ = 'GPL 2.0/LGPL 2.1' - -import unittest -import os - -PROJECT_DIR = lambda base : os.path.join(os.path.dirname(__file__), base).replace('\\','/') - -PRINT_INFO = True - -TEST_USERNAME = 'admin' -TEST_PASSWORD = 'test' -USERS_CREATED = False - -def print_info(func): - """ - Prints some useful info. - """ - if not PRINT_INFO: - return func - - def inner(self, *args, **kwargs): - result = func(self, *args, **kwargs) - - print('\n\n{0}'.format(func.__name__)) - print('============================') - if func.__doc__: - print('""" {0} """'.format(func.__doc__.strip())) - print('----------------------------') - if result is not None: - print(result) - print('\n++++++++++++++++++++++++++++') - - return result - return inner - -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -import random - -import sys - -PY3 = sys.version_info[0] == 3 - -if PY3: - from string import punctuation -else: - from string import translate, maketrans, punctuation - -import radar - -FACTORY = """ - Sed dictum in tellus non iaculis. Aenean ac interdum ipsum. Etiam tempor quis ante vel rhoncus. Nulla - facilisi. Curabitur iaculis consequat odio ut imperdiet? Integer accumsan; nisl vitae fermentum malesuada, - sapien nulla sodales orci, et elementum lacus purus vel purus! Nullam orci neque, tristique in porta id, - pretium ac sem. Fusce non est risus. Fusce convallis tellus augue, quis volutpat tellus dapibus sagittis. - Integer lacinia commodo risus vel cursus. Etiam vitae dui in dolor porta luctus sed id elit. Nulla et est - nec magna facilisis sagittis. Praesent tincidunt dictum lectus, sed aliquam eros. Donec placerat tortor ut - lorem facilisis congue. Quisque ac risus nibh. Etiam ultrices nibh justo; sed mollis ipsum dapibus vitae.Ut - vitae molestie erat. Mauris ac justo quis ante posuere vehicula. Vivamus accumsan mi volutpat diam lacinia, - vitae semper lectus pharetra. Cras ultrices arcu nec viverra consectetur. Cras placerat ante quis dui - consequat cursus. Nulla at enim dictum, consectetur ligula eget, vehicula nisi. Suspendisse eu ligula vitae - est tristique accumsan nec adipiscing risus.Donec tempus dui eget mollis fringilla. Fusce eleifend lacus lectus, - vel ornare felis lacinia ut. Morbi vel adipiscing augue. Vestibulum ante ipsum primis in faucibus orci luctus et - ultrices posuere cubilia Curae; Cras mattis pulvinar lacus, vitae pulvinar magna egestas non. Aliquam in urna - quis leo feugiat faucibus. Aliquam erat volutpat. Maecenas non mauris libero. Suspendisse nisi lorem, cursus a - tristique a, porttitor in nisl. Mauris pellentesque gravida mi non mattis. Cras mauris ligula, interdum semper - tincidunt sed, ornare a ipsum. Nulla ultrices tempus tortor vitae vehicula.Etiam at augue suscipit, vehicula - sapien sit amet; eleifend orci. Etiam venenatis leo nec cursus mattis. Nulla suscipit nec lorem et lobortis. - Donec interdum vehicula massa sed aliquam. Praesent eleifend mi sed mi pretium pellentesque. In in nisi tincidunt, - commodo lorem quis; tincidunt nisl. In suscipit quam a vehicula tincidunt! Fusce vitae varius nunc. Proin at - ipsum ac tellus hendrerit ultricies. Phasellus auctor hendrerit sapien viverra facilisis. Suspendisse lacus erat, - cursus at dolor in, vulputate convallis sapien. Etiam augue nunc, lobortis vel viverra sit amet, pretium et - lacus.Pellentesque elementum lectus eget massa tempus elementum? Nulla nec auctor dolor. Aliquam congue purus - quis libero fermentum cursus. Etiam quis massa ac nisl accumsan convallis vitae ac augue. Mauris neque est, - posuere quis dolor non, volutpat gravida tortor. Cum sociis natoque penatibus et magnis dis parturient montes, - nascetur ridiculus mus. Vivamus ullamcorper, urna at ultrices aliquam, orci libero gravida ligula, non pulvinar - sem magna sed tortor. Sed elementum leo viverra ipsum aliquet convallis. Suspendisse scelerisque auctor sapien. - Mauris enim nisl, sollicitudin at rhoncus vitae, convallis nec mauris. Phasellus sollicitudin dui ut luctus - consectetur. Vivamus placerat, neque id sagittis porttitor, nunc quam varius dolor, sit amet egestas nulla - risus eu odio. Mauris gravida eleifend laoreet. Aenean a nulla nisl. Integer pharetra magna adipiscing, imperdiet - augue ac, blandit felis. Cras id aliquam neque, vel consequat sapien.Duis eget vulputate ligula. Aliquam ornare - dui non nunc laoreet, non viverra dolor semper. Aenean ullamcorper velit sit amet dignissim fermentum! Aenean urna - leo, rutrum volutpat mauris nec, facilisis molestie tortor. In convallis pellentesque lorem, a lobortis erat - molestie et! Ut sed sem a odio aliquam elementum. Morbi pretium velit libero, adipiscing consequat leo dignissim - eu. Mauris vestibulum feugiat risus; quis pharetra purus tincidunt quis. Morbi semper tincidunt lorem id iaculis. - Quisque non pulvinar magna. Morbi consequat eleifend neque et iaculis. Fusce non laoreet urna. Donec ut nunc - ultrices, fringilla nunc ut, tempor elit. Phasellus semper sapien augue, in gravida neque egestas at. - Integer dapibus lacus vitae luctus sagittis! Suspendisse imperdiet tortor eget mattis consectetur. Aliquam viverra - purus a quam lacinia euismod. Nunc non consequat mi; ac vehicula lacus. Pellentesque accumsan ac diam in fermentum! - Maecenas quis nibh sed dolor adipiscing facilisis. Aenean vel arcu eu est fermentum egestas vulputate eget purus. - Sed fermentum rhoncus dapibus. Quisque molestie magna eu accumsan lobortis. Vestibulum cursus euismod posuere. - Aliquam eu dapibus urna. Nulla id accumsan justo. Vivamus vitae ullamcorper tellus. Class aptent taciti sociosqu - ad litora torquent per conubia nostra, per inceptos himenaeos.Donec pulvinar tempus lectus vitae ultricies. - Vestibulum sagittis orci quis risus ultricies feugiat. Nunc feugiat velit est, at aliquam massa tristique eu. - Aenean quis enim vel leo vestibulum volutpat in non elit. Quisque molestie tincidunt purus; ac lacinia mauris - rhoncus in. Nullam id arcu at mauris varius viverra ut vitae massa. In ac nunc ipsum. Proin consectetur urna sit - amet mattis vulputate. Nullam lacinia pretium tempus. Aenean quis ornare metus, tempus volutpat neque. Mauris - volutpat scelerisque augue; at lobortis nulla rhoncus vitae. Mauris at lobortis turpis. Vivamus et ultrices lacus. - Donec fermentum neque in eros cursus, ac tincidunt sapien consequat. Curabitur varius commodo rutrum. Nulla - facilisi. Ut feugiat dui nec turpis sodales aliquam. Quisque auctor vestibulum condimentum. Quisque nec eros - lorem. Curabitur et felis nec diam dictum ultrices vestibulum ac eros! Quisque eu pretium lacus. Morbi bibendum - sagittis rutrum. Nam eget tellus quam. Nullam pharetra vestibulum justo. Donec molestie urna et scelerisque - laoreet? Sed consectetur pretium hendrerit. Quisque erat nulla, elementum sit amet nibh vel, posuere pulvinar - nulla. Donec elementum adipiscing dictum! Nam euismod semper nisi, eu lacinia felis placerat vel! Praesent eget - dapibus turpis, et fringilla elit. Maecenas quis nunc cursus felis fringilla consequat! Cum sociis natoque - penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed ullamcorper libero quis nisl sollicitudin, - ut pulvinar arcu consectetur. Donec nisi nibh, condimentum et lectus non, accumsan imperdiet ipsum. Maecenas vitae - massa eget lorem ornare dignissim. Nullam condimentum mauris id quam tincidunt venenatis. Aenean mattis viverra - sem, vitae luctus velit rhoncus non. Vestibulum leo justo, rhoncus at aliquam et, iaculis sed dolor. Integer - bibendum vitae urna in ornare! Cras accumsan nulla eu libero tempus, in dignissim augue imperdiet. Vivamus a - lacinia odio. Curabitur id egestas eros. Integer non rutrum est. In nibh sem, tempus ac dignissim vel, ornare ac - mi. Nulla congue scelerisque est nec commodo. Phasellus turpis lorem, sodales quis sem id, facilisis commodo - massa. Vestibulum ultrices dolor eget purus semper euismod? Fusce id congue leo. Quisque dui magna, ullamcorper - et leo eget, commodo facilisis ipsum. Curabitur congue vitae risus nec posuere. Phasellus tempor ligula in nisl - pellentesque mattis. Sed nunc turpis, pharetra vel leo ac, lacinia cursus risus. Quisque congue aliquet volutpat. - Integer dictum est quis semper tristique. Donec feugiat vestibulum tortor, id fringilla nisi lobortis eu. Nam - hendrerit egestas sem, non mollis tortor iaculis quis. Phasellus id aliquet erat. Nunc facilisis nisi dolor, - quis semper dui euismod vel. Cras convallis bibendum tortor malesuada tincidunt. Sed urna quam, pellentesque - eget eleifend ac, consequat bibendum urna. Sed fringilla elit hendrerit leo blandit laoreet eget quis quam! - Morbi eu leo a dolor aliquet dictum. Suspendisse condimentum mauris non ipsum rhoncus, sit amet hendrerit augue - gravida. Quisque facilisis pharetra felis faucibus gravida. In arcu neque, gravida ut fermentum ut, placerat eu - quam. Nullam aliquet lectus mauris, quis dignissim est mollis sed. Ut vestibulum laoreet eros quis cursus. Proin - commodo eros in mollis mollis. Mauris bibendum cursus nibh, sit amet eleifend mauris luctus vitae. Sed aliquet - pretium tristique. Morbi ultricies augue a lacinia porta. Nullam mollis erat non imperdiet imperdiet. Etiam - tincidunt fringilla ligula, in adipiscing libero viverra eu. Nunc gravida hendrerit massa, in pellentesque nunc - dictum id. - """ - -if PY3: - split_words = lambda f: list(set(f.lower().translate(str.maketrans("", "", punctuation)).split())) -else: - split_words = lambda f: list(set(translate(f.lower(), maketrans(punctuation, ' ' * len(punctuation))).split())) - -split_sentences = lambda f: f.split('?') -change_date = lambda: bool(random.randint(0, 1)) - -WORDS = split_words(FACTORY) -SENTENCES = split_sentences(FACTORY) -NUM_ITEMS = 50 - -_ = lambda s: s - -# Skipping from non-Django tests. -if os.environ.get("DJANGO_SETTINGS_MODULE", None): - - from django.conf import settings - - try: - from django.utils.text import slugify - except ImportError: - from django.template.defaultfilters import slugify - - from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION - from django.contrib.auth.models import User - from django.db import IntegrityError - from django.contrib.contenttypes.models import ContentType - from django.db import models - - # ************************************************************************* - # **************** Safe User import for Django > 1.5, < 1.8 *************** - # ************************************************************************* - from nine.user import User - - from foo.models import FooItem, Foo2Item, Foo3Item, Foo4Item - - def create_users(): - if not USERS_CREATED: - data = [ - { - u'email': u'admin.v4@foreverchild.info', - u'first_name': u'', - u'is_active': True, - u'is_staff': True, - u'is_superuser': True, - u'last_name': u'', - u'username': TEST_USERNAME - }, - { - u'email': u'artur.barseghyan@gmail.com', - u'first_name': u'Artur', - u'is_active': True, - u'is_staff': True, - u'is_superuser': False, - u'last_name': u'Barseghyan', - u'username': u'arturbarseghyan' - }, - { - u'email': u'john.doe@example.com', - u'first_name': u'John', - u'is_active': True, - u'is_staff': True, - u'is_superuser': False, - u'last_name': u'Doe', - u'username': u'john.doe' - }, - { - u'email': u'johnatan@example.com', - u'first_name': u'Johnatan', - u'is_active': True, - u'is_staff': True, - u'is_superuser': False, - u'last_name': u'Livingstone', - u'username': u'johnatan' - }, - { - u'email': u'oscar@example.com', - u'first_name': u'Oscar', - u'is_active': True, - u'is_staff': True, - u'is_superuser': False, - u'last_name': u'', - u'username': u'oscar' - }, - { - u'email': u'charlie@example.com', - u'first_name': u'Charlie', - u'is_active': True, - u'is_staff': True, - u'is_superuser': False, - u'last_name': u'Big Potatoe', - u'username': u'charlie' - } - ] - - for user_data_dict in data: - user = User() - - for prop, value in user_data_dict.items(): - setattr(user, prop, value) - - #print(user.__dict__) - #print("set password {0} for user {1}".format(TEST_PASSWORD, user.username)) - user.set_password(TEST_PASSWORD) - try: - user.save() - except IntegrityError as err: - print("{0} {1}".format(err, user)) - except Exception as err: - print("{0} {1}".format(err, user)) - - MODEL_FACTORY = ( - FooItem, - Foo2Item, - Foo3Item, - Foo4Item - ) - - CHANGE_MESSAGE_FACTORY = ( - 'Changed title', - 'Changed slug', - 'Changed body', - 'Changed date_published', - ) - - def generate_data(num_items=NUM_ITEMS): - create_users() - - class CustomLogEntry(models.Model): - action_time = models.DateTimeField(_('action time')) - user = models.ForeignKey(User) - content_type = models.ForeignKey(ContentType, blank=True, null=True) - object_id = models.TextField(_('object id'), blank=True, null=True) - object_repr = models.CharField(_('object repr'), max_length=200) - action_flag = models.PositiveSmallIntegerField(_('action flag')) - change_message = models.TextField(_('change message'), blank=True) - - class Meta: - db_table = LogEntry._meta.db_table - - words = WORDS - - users = User.objects.all()[:] - - random_date = radar.random_datetime() - - for index in range(num_items): - # Saving an item to database - FooItemModel = MODEL_FACTORY[random.randint(0, len(MODEL_FACTORY) - 1)] - i = FooItemModel() - random_name = words[random.randint(0, len(words) - 1)] - - if PY3: - i.title = str(random_name).capitalize() - i.body = str(SENTENCES[random.randint(0, len(SENTENCES) - 1)]) - else: - i.title = unicode(random_name).capitalize() - i.body = unicode(SENTENCES[random.randint(0, len(SENTENCES) - 1)]) - - i.slug = slugify(i.title) - random_date = radar.random_datetime() if change_date() else random_date - i.date_published = random_date - - try: - i.save() - words.remove(random_name) - - if 0 == len(words): - words = WORDS - - except Exception as e: - print(e) - - try: - # Creating a ``LogEntry`` for the item created. - l = CustomLogEntry() - l.action_time = i.date_published - l.user = users[random.randint(0, len(users) - 1)] - l.content_type = ContentType._default_manager \ - .get_for_model(FooItemModel) - l.object_id = i.pk - - if PY3: - l.object_repr = str(i) - else: - l.object_repr = unicode(i) - - l.action_flag = ADDITION - l.save() - except Exception as e: - print(e) - -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - from django.test import LiveServerTestCase - #from django.contrib.auth.models import User - - from selenium.webdriver.firefox.webdriver import WebDriver - from selenium.webdriver.support.wait import WebDriverWait - - class AdminTimelineViewsTest(LiveServerTestCase): #unittest.TestCase - """ - Tests of ``admin_timeline.views.log`` module. - """ - - @classmethod - def setUpClass(cls): - cls.selenium = WebDriver() - super(AdminTimelineViewsTest, cls).setUpClass() - - # Create user if doesn't exist yet. - try: - u = User._default_manager.get(username=TEST_USERNAME) - except Exception as e: - print(e) - - # Create a user account - u = User() - u.username = TEST_USERNAME - u.set_password(TEST_PASSWORD) - u.email = 'admin@dev.example.com' - u.is_active = True - u.is_staff = True - u.is_superuser = True - - try: - u.save() - except IntegrityError as e: - print(e) - except Exception as e: - print(e) - - # Generate test data - try: - generate_data() - except Exception as e: - print(e) - - @classmethod - def tearDownClass(cls): - try: - cls.selenium.quit() - except Exception as e: - print(e) - - super(AdminTimelineViewsTest, cls).tearDownClass() - - @print_info - def test_01_login(self): - """ - Test login. - """ - create_users() - self.selenium.get('{0}{1}'.format(self.live_server_url, '/admin/')) - username_input = self.selenium.find_element_by_name("username") - username_input.send_keys(TEST_USERNAME) - password_input = self.selenium.find_element_by_name("password") - password_input.send_keys(TEST_PASSWORD) - #import ipdb; ipdb.set_trace() - self.selenium.find_element_by_xpath('//input[@value="Log in"]').click() - - @print_info - def test_02_view(self): - """ - Test view. - """ - create_users() - # Generate test data - try: - generate_data(num_items=10) - except Exception as e: - print(e) - - # Test login - self.selenium.get('{0}{1}'.format(self.live_server_url, '/admin/timeline/')) - username_input = self.selenium.find_element_by_name("username") - username_input.send_keys(TEST_USERNAME) - password_input = self.selenium.find_element_by_name("password") - password_input.send_keys(TEST_PASSWORD) - #import ipdb; ipdb.set_trace() - self.selenium.find_element_by_xpath('//input[@value="Log in"]').click() - - WebDriverWait(self.selenium, timeout=5).until( - lambda driver: driver.find_element_by_id('admin-timeline') - ) - - # Test view - workflow = [] - - container = self.selenium.find_element_by_id('admin-timeline') - self.assertTrue(container is not None) - workflow.append(container) - - item = self.selenium.find_element_by_xpath('//li[@class="date-entry"]') - self.assertTrue(item is not None) - workflow.append(item) - - return workflow - - -if __name__ == "__main__": - # Tests - unittest.main() diff --git a/src/admin_timeline/tests/__init__.py b/src/admin_timeline/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/admin_timeline/tests/base.py b/src/admin_timeline/tests/base.py new file mode 100644 index 0000000..6b7b8be --- /dev/null +++ b/src/admin_timeline/tests/base.py @@ -0,0 +1,168 @@ +import random +import sys + +PY3 = sys.version_info[0] == 3 + +if PY3: + from string import punctuation +else: + from string import translate, maketrans, punctuation + +__title__ = 'admin_timeline.tests.base' +__author__ = 'Artur Barseghyan ' +__copyright__ = '2013-2016 Artur Barseghyan' +__license__ = 'GPL 2.0/LGPL 2.1' +__all__ = ( + 'FACTORY', + 'split_words', +) + +FACTORY = """ + Sed dictum in tellus non iaculis. Aenean ac interdum ipsum. Etiam tempor + quis ante vel rhoncus. Nulla facilisi. Curabitur iaculis consequat odio ut + imperdiet? Integer accumsan; nisl vitae fermentum malesuada, sapien nulla + sodales orci, et elementum lacus purus vel purus! Nullam orci neque, + tristique in porta id, pretium ac sem. Fusce non est risus. Fusce + convallis tellus augue, quis volutpat tellus dapibus sagittis. Integer + lacinia commodo risus vel cursus. Etiam vitae dui in dolor porta luctus + sed id elit. Nulla et est nec magna facilisis sagittis. Praesent tincidunt + dictum lectus, sed aliquam eros. Donec placerat tortor ut lorem facilisis + congue. Quisque ac risus nibh. Etiam ultrices nibh justo; sed mollis + ipsum dapibus vitae. Ut vitae molestie erat. Mauris ac justo quis ante + posuere vehicula. Vivamus accumsan mi volutpat diam lacinia, vitae semper + lectus pharetra. Cras ultrices arcu nec viverra consectetur. Cras placerat + ante quis dui consequat cursus. Nulla at enim dictum, consectetur ligula + eget, vehicula nisi. Suspendisse eu ligula vitae est tristique accumsan + nec adipiscing risus. Donec tempus dui eget mollis fringilla. Fusce + eleifend lacus lectus, vel ornare felis lacinia ut. Morbi vel adipiscing + augue. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices + posuere cubilia Curae; Cras mattis pulvinar lacus, vitae pulvinar magna + egestas non. Aliquam in urna quis leo feugiat faucibus. Aliquam erat + volutpat. Maecenas non mauris libero. Suspendisse nisi lorem, cursus a + tristique a, porttitor in nisl. Mauris pellentesque gravida mi non mattis. + Cras mauris ligula, interdum semper tincidunt sed, ornare a ipsum. Nulla + ultrices tempus tortor vitae vehicula. Etiam at augue suscipit, vehicula + sapien sit amet; eleifend orci. Etiam venenatis leo nec cursus mattis. + Nulla suscipit nec lorem et lobortis. Donec interdum vehicula massa sed + aliquam. Praesent eleifend mi sed mi pretium pellentesque. In in nisi + tincidunt, commodo lorem quis; tincidunt nisl. In suscipit quam a vehicula + tincidunt! Fusce vitae varius nunc. Proin at ipsum ac tellus hendrerit + ultricies. Phasellus auctor hendrerit sapien viverra facilisis. + Suspendisse lacus erat, cursus at dolor in, vulputate convallis sapien. + Etiam augue nunc, lobortis vel viverra sit amet, pretium et lacus. + Pellentesque elementum lectus eget massa tempus elementum? Nulla nec + auctor dolor. Aliquam congue purus quis libero fermentum cursus. Etiam + quis massa ac nisl accumsan convallis vitae ac augue. Mauris neque est, + posuere quis dolor non, volutpat gravida tortor. Cum sociis natoque + penatibus et magnis dis parturient montes, nascetur ridiculus mus. + Vivamus ullamcorper, urna at ultrices aliquam, orci libero gravida ligula, + non pulvinar sem magna sed tortor. Sed elementum leo viverra ipsum aliquet + convallis. Suspendisse scelerisque auctor sapien. Mauris enim nisl, + sollicitudin at rhoncus vitae, convallis nec mauris. Phasellus + sollicitudin dui ut luctus consectetur. Vivamus placerat, neque id + sagittis porttitor, nunc quam varius dolor, sit amet egestas nulla risus + eu odio. Mauris gravida eleifend laoreet. Aenean a nulla nisl. Integer + pharetra magna adipiscing, imperdiet augue ac, blandit felis. Cras id + aliquam neque, vel consequat sapien.Duis eget vulputate ligula. Aliquam + ornare dui non nunc laoreet, non viverra dolor semper. Aenean ullamcorper + velit sit amet dignissim fermentum! Aenean urna leo, rutrum volutpat + mauris nec, facilisis molestie tortor. In convallis pellentesque lorem, + a lobortis erat molestie et! Ut sed sem a odio aliquam elementum. Morbi + pretium velit libero, adipiscing consequat leo dignissim eu. Mauris + vestibulum feugiat risus; quis pharetra purus tincidunt quis. Morbi semper + tincidunt lorem id iaculis. Quisque non pulvinar magna. Morbi consequat + eleifend neque et iaculis. Fusce non laoreet urna. Donec ut nunc ultrices, + fringilla nunc ut, tempor elit. Phasellus semper sapien augue, in gravida + neque egestas at. Integer dapibus lacus vitae luctus sagittis! Suspendisse + imperdiet tortor eget mattis consectetur. Aliquam viverra purus a quam + lacinia euismod. Nunc non consequat mi; ac vehicula lacus. Pellentesque + accumsan ac diam in fermentum! Maecenas quis nibh sed dolor adipiscing + facilisis. Aenean vel arcu eu est fermentum egestas vulputate eget purus. + Sed fermentum rhoncus dapibus. Quisque molestie magna eu accumsan lobortis. + Vestibulum cursus euismod posuere. Aliquam eu dapibus urna. Nulla id + accumsan justo. Vivamus vitae ullamcorper tellus. Class aptent taciti + sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. + Donec pulvinar tempus lectus vitae ultricies. Vestibulum sagittis orci + quis risus ultricies feugiat. Nunc feugiat velit est, at aliquam massa + tristique eu. Aenean quis enim vel leo vestibulum volutpat in non elit. + Quisque molestie tincidunt purus; ac lacinia mauris rhoncus in. Nullam id + arcu at mauris varius viverra ut vitae massa. In ac nunc ipsum. Proin + consectetur urna sit amet mattis vulputate. Nullam lacinia pretium tempus. + Aenean quis ornare metus, tempus volutpat neque. Mauris volutpat + scelerisque augue; at lobortis nulla rhoncus vitae. Mauris at lobortis + turpis. Vivamus et ultrices lacus. Donec fermentum neque in eros cursus, + ac tincidunt sapien consequat. Curabitur varius commodo rutrum. Nulla + facilisi. Ut feugiat dui nec turpis sodales aliquam. Quisque auctor + vestibulum condimentum. Quisque nec eros lorem. Curabitur et felis nec + diam dictum ultrices vestibulum ac eros! Quisque eu pretium lacus. Morbi + bibendum sagittis rutrum. Nam eget tellus quam. Nullam pharetra vestibulum + justo. Donec molestie urna et scelerisque laoreet? Sed consectetur pretium + hendrerit. Quisque erat nulla, elementum sit amet nibh vel, posuere + pulvinar nulla. Donec elementum adipiscing dictum! Nam euismod semper + nisi, eu lacinia felis placerat vel! Praesent eget dapibus turpis, et + fringilla elit. Maecenas quis nunc cursus felis fringilla consequat! Cum + sociis natoque penatibus et magnis dis parturient montes, nascetur + ridiculus mus. Sed ullamcorper libero quis nisl sollicitudin, ut pulvinar + arcu consectetur. Donec nisi nibh, condimentum et lectus non, accumsan + imperdiet ipsum. Maecenas vitae massa eget lorem ornare dignissim. Nullam + condimentum mauris id quam tincidunt venenatis. Aenean mattis viverra sem, + vitae luctus velit rhoncus non. Vestibulum leo justo, rhoncus at aliquam + et, iaculis sed dolor. Integer bibendum vitae urna in ornare! Cras + accumsan nulla eu libero tempus, in dignissim augue imperdiet. Vivamus a + lacinia odio. Curabitur id egestas eros. Integer non rutrum est. In nibh + sem, tempus ac dignissim vel, ornare ac mi. Nulla congue scelerisque est + nec commodo. Phasellus turpis lorem, sodales quis sem id, facilisis + commodo massa. Vestibulum ultrices dolor eget purus semper euismod? Fusce + id congue leo. Quisque dui magna, ullamcorper et leo eget, commodo + facilisis ipsum. Curabitur congue vitae risus nec posuere. Phasellus + tempor ligula in nisl pellentesque mattis. Sed nunc turpis, pharetra vel + leo ac, lacinia cursus risus. Quisque congue aliquet volutpat. Integer + dictum est quis semper tristique. Donec feugiat vestibulum tortor, id + fringilla nisi lobortis eu. Nam hendrerit egestas sem, non mollis tortor + iaculis quis. Phasellus id aliquet erat. Nunc facilisis nisi dolor, quis + semper dui euismod vel. Cras convallis bibendum tortor malesuada + tincidunt. Sed urna quam, pellentesque eget eleifend ac, consequat + bibendum urna. Sed fringilla elit hendrerit leo blandit laoreet eget quis + quam! Morbi eu leo a dolor aliquet dictum. Suspendisse condimentum mauris + non ipsum rhoncus, sit amet hendrerit augue gravida. Quisque facilisis + pharetra felis faucibus gravida. In arcu neque, gravida ut fermentum ut, + placerat eu quam. Nullam aliquet lectus mauris, quis dignissim est mollis + sed. Ut vestibulum laoreet eros quis cursus. Proin commodo eros in mollis + mollis. Mauris bibendum cursus nibh, sit amet eleifend mauris luctus + vitae. Sed aliquet pretium tristique. Morbi ultricies augue a lacinia + porta. Nullam mollis erat non imperdiet imperdiet. Etiam tincidunt + fringilla ligula, in adipiscing libero viverra eu. Nunc gravida hendrerit + massa, in pellentesque nunc dictum id. + """ + +if PY3: + def split_words(val): + return list( + set( + val.lower().translate( + str.maketrans("", "", punctuation) + ).split() + ) + ) +else: + def split_words(val): + return list( + set( + translate( + val.lower(), maketrans(punctuation, ' ' * len(punctuation)) + ).split() + ) + ) + + +def split_sentences(val): + return val.split('?') + + +def change_date(): + return bool(random.randint(0, 1)) + + +WORDS = split_words(FACTORY) +SENTENCES = split_sentences(FACTORY) +NUM_ITEMS = 50 diff --git a/src/admin_timeline/tests/data.py b/src/admin_timeline/tests/data.py new file mode 100644 index 0000000..506facf --- /dev/null +++ b/src/admin_timeline/tests/data.py @@ -0,0 +1,70 @@ +__title__ = 'admin_timeline.tests.test_core' +__author__ = 'Artur Barseghyan ' +__copyright__ = '2013-2016 Artur Barseghyan' +__license__ = 'GPL 2.0/LGPL 2.1' +__all__ = ( + 'TEST_USERNAME', + 'TEST_PASSWORD', + 'USERS_CREATED', + 'TEST_USERS_DATA', +) + +TEST_USERNAME = 'admin' +TEST_PASSWORD = 'test' +USERS_CREATED = False +TEST_USERS_DATA = [ + { + u'email': u'admin.v4@foreverchild.info', + u'first_name': u'', + u'is_active': True, + u'is_staff': True, + u'is_superuser': True, + u'last_name': u'', + u'username': TEST_USERNAME + }, + { + u'email': u'artur.barseghyan@gmail.com', + u'first_name': u'Artur', + u'is_active': True, + u'is_staff': True, + u'is_superuser': False, + u'last_name': u'Barseghyan', + u'username': u'arturbarseghyan' + }, + { + u'email': u'john.doe@example.com', + u'first_name': u'John', + u'is_active': True, + u'is_staff': True, + u'is_superuser': False, + u'last_name': u'Doe', + u'username': u'john.doe' + }, + { + u'email': u'johnatan@example.com', + u'first_name': u'Johnatan', + u'is_active': True, + u'is_staff': True, + u'is_superuser': False, + u'last_name': u'Livingstone', + u'username': u'johnatan' + }, + { + u'email': u'oscar@example.com', + u'first_name': u'Oscar', + u'is_active': True, + u'is_staff': True, + u'is_superuser': False, + u'last_name': u'', + u'username': u'oscar' + }, + { + u'email': u'charlie@example.com', + u'first_name': u'Charlie', + u'is_active': True, + u'is_staff': True, + u'is_superuser': False, + u'last_name': u'Big Potatoe', + u'username': u'charlie' + } +] diff --git a/src/admin_timeline/tests/helpers.py b/src/admin_timeline/tests/helpers.py new file mode 100644 index 0000000..c8632fd --- /dev/null +++ b/src/admin_timeline/tests/helpers.py @@ -0,0 +1,46 @@ +from __future__ import print_function + +import logging +import os + +__title__ = 'admin_timeline.tests.helpers' +__author__ = 'Artur Barseghyan ' +__copyright__ = '2013-2016 Artur Barseghyan' +__license__ = 'GPL 2.0/LGPL 2.1' + +logger = logging.getLogger(__name__) + + +def project_dir(base): + return os.path.join(os.path.dirname(__file__), base).replace('\\', '/') + + +PROJECT_DIR = project_dir + +LOG_INFO = True + + +def log_info(func): + """Log some useful info.""" + if not LOG_INFO: + return func + + def inner(self, *args, **kwargs): + result = func(self, *args, **kwargs) + + logger.debug('\n\n{0}'.format(func.__name__)) + logger.debug('============================') + if func.__doc__: + logger.debug('""" {0} """'.format(func.__doc__.strip())) + logger.debug('----------------------------') + if result is not None: + logger.debug(result) + logger.debug('\n++++++++++++++++++++++++++++') + + return result + return inner + + +def _(val): + """Return value as is.""" + return val diff --git a/src/admin_timeline/tests/test_core.py b/src/admin_timeline/tests/test_core.py new file mode 100644 index 0000000..05594ef --- /dev/null +++ b/src/admin_timeline/tests/test_core.py @@ -0,0 +1,283 @@ +import logging +import os +import random +import unittest + +import radar + +from six import text_type + +from .helpers import _, log_info +from .base import NUM_ITEMS, WORDS, SENTENCES, change_date +from .data import TEST_PASSWORD, TEST_USERNAME, USERS_CREATED, TEST_USERS_DATA + +__title__ = 'admin_timeline.tests.test_core' +__author__ = 'Artur Barseghyan ' +__copyright__ = '2013-2016 Artur Barseghyan' +__license__ = 'GPL 2.0/LGPL 2.1' + +logger = logging.getLogger(__name__) + + +# Skipping from non-Django tests. +if os.environ.get("DJANGO_SETTINGS_MODULE", None): + + from django.conf import settings + + try: + from django.utils.text import slugify + except ImportError: + from django.template.defaultfilters import slugify + + from django.contrib.admin.models import ( + LogEntry, + ADDITION, + # CHANGE, + # DELETION + ) + from django.contrib.auth.models import User + from django.db import models, IntegrityError + from django.contrib.contenttypes.models import ContentType + + # ************************************************************************ + # **************** Safe User import for Django > 1.5, < 1.8 ************** + # ************************************************************************ + from nine.user import User + + from foo.models import FooItem, Foo2Item, Foo3Item, Foo4Item + + def create_users(): + if not USERS_CREATED: + + for user_data_dict in TEST_USERS_DATA: + user = User() + + for prop, value in user_data_dict.items(): + setattr(user, prop, value) + + user.set_password(TEST_PASSWORD) + try: + user.save() + except IntegrityError as err: + logger.debug("{0} {1}".format(err, user)) + except Exception as err: + logger.debug("{0} {1}".format(err, user)) + + MODEL_FACTORY = ( + FooItem, + Foo2Item, + Foo3Item, + Foo4Item + ) + + CHANGE_MESSAGE_FACTORY = ( + 'Changed title', + 'Changed slug', + 'Changed body', + 'Changed date_published', + ) + + def generate_data(num_items=NUM_ITEMS): + create_users() + + class CustomLogEntry(models.Model): + """Custom log entry.""" + + action_time = models.DateTimeField(_('action time')) + user = models.ForeignKey(User) + content_type = models.ForeignKey(ContentType, blank=True, + null=True) + object_id = models.TextField(_('object id'), blank=True, + null=True) + object_repr = models.CharField(_('object repr'), max_length=200) + action_flag = models.PositiveSmallIntegerField(_('action flag')) + change_message = models.TextField(_('change message'), blank=True) + + class Meta: + """Class meta.""" + + db_table = LogEntry._meta.db_table + + words = WORDS[:] + + users = User.objects.all()[:] + + random_date = radar.random_datetime() + + for index in range(num_items): + # Saving an item to database + foo_item_model_cls = MODEL_FACTORY[random.randint( + 0, len(MODEL_FACTORY) - 1 + )] + item = foo_item_model_cls() + random_name = words[random.randint(0, len(words) - 1)] + + item.title = text_type(random_name).capitalize() + item.body = text_type( + SENTENCES[random.randint(0, len(SENTENCES) - 1)] + ) + + item.slug = slugify(item.title) + random_date = radar.random_datetime() \ + if change_date() \ + else random_date + item.date_published = random_date + + try: + item.save() + words.remove(random_name) + + if 0 == len(words): + words = WORDS[:] + + except Exception as err: + logger.debug(err) + + try: + # Creating a ``LogEntry`` for the item created. + log_entry = CustomLogEntry() + log_entry.action_time = item.date_published + log_entry.user = users[random.randint(0, len(users) - 1)] + log_entry.content_type = ContentType._default_manager \ + .get_for_model( + foo_item_model_cls + ) + log_entry.object_id = item.pk + log_entry.object_repr = text_type(item) + + log_entry.action_flag = ADDITION + log_entry.save() + except Exception as err: + logger.debug(err) + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + from django.test import LiveServerTestCase + + from selenium import webdriver + from selenium.webdriver.firefox.firefox_binary import FirefoxBinary + # from selenium.webdriver.firefox.webdriver import WebDriver + from selenium.webdriver.support.wait import WebDriverWait + from selenium.common.exceptions import WebDriverException + + class AdminTimelineViewsTest(LiveServerTestCase): + """Tests of ``admin_timeline.views.log`` module.""" + + @classmethod + def setUpClass(cls): + firefox_bin_path = getattr(settings, 'FIREFOX_BIN_PATH', None) + phantom_js_executable_path = getattr( + settings, 'PHANTOM_JS_EXECUTABLE_PATH', None + ) + if phantom_js_executable_path is not None: + if phantom_js_executable_path: + cls.selenium = webdriver.PhantomJS( + executable_path=phantom_js_executable_path + ) + else: + cls.selenium = webdriver.PhantomJS() + elif firefox_bin_path: + binary = FirefoxBinary(firefox_bin_path) + cls.selenium = webdriver.Firefox(firefox_binary=binary) + else: + cls.selenium = webdriver.Firefox() + + super(AdminTimelineViewsTest, cls).setUpClass() + + # Create user if doesn't exist yet. + try: + user = User._default_manager.get(username=TEST_USERNAME) + except Exception as err: + logger.debug(err) + + # Create a user account + user = User() + user.username = TEST_USERNAME + user.set_password(TEST_PASSWORD) + user.email = 'admin@dev.example.com' + user.is_active = True + user.is_staff = True + user.is_superuser = True + + try: + user.save() + except IntegrityError as err: + logger.debug(err) + except Exception as err: + logger.debug(err) + + # Generate test data + try: + generate_data() + except Exception as err: + logger.debug(err) + + @classmethod + def tearDownClass(cls): + try: + cls.selenium.quit() + except Exception as err: + logger.debug(err) + + super(AdminTimelineViewsTest, cls).tearDownClass() + + def setUp(self): + create_users() + generate_data(num_items=10) + + @log_info + def test_01_login(self): + """Test login.""" + self.selenium.get('{0}{1}'.format(self.live_server_url, '/admin/')) + username_input = self.selenium.find_element_by_name("username") + username_input.send_keys(TEST_USERNAME) + password_input = self.selenium.find_element_by_name("password") + password_input.send_keys(TEST_PASSWORD) + self.selenium.find_element_by_xpath( + '//input[@value="Log in"]').click() + + @log_info + def test_02_view(self): + """Test view.""" + # create_users() + # # Generate test data + # try: + # generate_data(num_items=10) + # except Exception as err: + # logger.debug(err) + + # Test login + self.selenium.get( + '{0}{1}'.format(self.live_server_url, '/admin/timeline/') + ) + username_input = self.selenium.find_element_by_name("username") + username_input.send_keys(TEST_USERNAME) + password_input = self.selenium.find_element_by_name("password") + password_input.send_keys(TEST_PASSWORD) + self.selenium.find_element_by_xpath( + '//input[@value="Log in"]').click() + + WebDriverWait(self.selenium, timeout=5).until( + lambda driver: driver.find_element_by_id('admin-timeline') + ) + + # Test view + workflow = [] + + container = self.selenium.find_element_by_id('admin-timeline') + self.assertTrue(container is not None) + workflow.append(container) + + item = self.selenium.find_element_by_xpath( + '//li[@class="date-entry"]') + self.assertTrue(item is not None) + workflow.append(item) + + return workflow + + +if __name__ == "__main__": + # Tests + unittest.main() diff --git a/src/admin_timeline/urls.py b/src/admin_timeline/urls.py index f78ea57..e59dd39 100644 --- a/src/admin_timeline/urls.py +++ b/src/admin_timeline/urls.py @@ -1,12 +1,13 @@ +from django.conf.urls import url + +from .views import log + __title__ = 'admin_timeline.urls' __author__ = 'Artur Barseghyan ' -__copyright__ = 'Copyright (c) 2013-2015 Artur Barseghyan' +__copyright__ = '2013-2016 Artur Barseghyan' __license__ = 'GPL 2.0/LGPL 2.1' __all__ = ('urlpatterns',) -from django.conf.urls import url - -from admin_timeline.views import log urlpatterns = [ url(r'^$', view=log, name='admin_timeline.log'), diff --git a/src/admin_timeline/views.py b/src/admin_timeline/views.py index 83915c7..6cf2197 100644 --- a/src/admin_timeline/views.py +++ b/src/admin_timeline/views.py @@ -1,37 +1,45 @@ -__title__ = 'admin_timeline.views' -__author__ = 'Artur Barseghyan ' -__copyright__ = 'Copyright (c) 2013-2015 Artur Barseghyan' -__license__ = 'GPL 2.0/LGPL 2.1' -__all__ = ('log',) - -import json import datetime +import json -from django.http import HttpResponse -from django.views.decorators.cache import never_cache -from django.contrib.admin.views.decorators import staff_member_required from django.contrib.admin.models import LogEntry -from django.shortcuts import render_to_response +from django.contrib.admin.views.decorators import staff_member_required +from django.http import HttpResponse +# from django.shortcuts import render_to_response from django.template import RequestContext from django.template.loader import render_to_string from django.template.defaultfilters import date as date_format from django.utils.translation import ugettext_lazy as _ +from django.views.decorators.cache import never_cache from django.views.decorators.csrf import csrf_exempt -from admin_timeline.settings import ( - NUMBER_OF_ENTRIES_PER_PAGE, SINGLE_LOG_ENTRY_DATE_FORMAT - ) -from admin_timeline.settings import LOG_ENTRIES_DAY_HEADINGS_DATE_FORMAT -from admin_timeline.forms import FilterForm -from admin_timeline.compat import TEMPLATE_NAME, TEMPLATE_NAME_AJAX +from nine import versions + +from .compat import TEMPLATE_NAME, TEMPLATE_NAME_AJAX +from .forms import FilterForm +from .settings import ( + NUMBER_OF_ENTRIES_PER_PAGE, + SINGLE_LOG_ENTRY_DATE_FORMAT, + LOG_ENTRIES_DAY_HEADINGS_DATE_FORMAT +) + +if versions.DJANGO_GTE_1_10: + from django.shortcuts import render +else: + from django.shortcuts import render_to_response + +__title__ = 'admin_timeline.views' +__author__ = 'Artur Barseghyan ' +__copyright__ = '2013-2016 Artur Barseghyan' +__license__ = 'GPL 2.0/LGPL 2.1' +__all__ = ('log',) + @csrf_exempt @never_cache @staff_member_required -def log(request, template_name=TEMPLATE_NAME, \ +def log(request, template_name=TEMPLATE_NAME, template_name_ajax=TEMPLATE_NAME_AJAX): - """ - Get number of log entires. Serves both non-AJAX and AJAX driven requests. + """Get log entries. Serves both non-AJAX and AJAX driven requests. Since we have a breakdown of entries per day per entry and we have an AJAX driven infinite scroll and we want to avoid having duplicated date headers, @@ -65,23 +73,22 @@ def log(request, template_name=TEMPLATE_NAME, \ NOTE: If it gets too complicatd with filtering, we need to have forms to validate and process the POST data. """ - def _get_date_from_string(s): - """ - Gets date from a string given. + def _get_date_from_string(val): + """Get date from a string given. - :param s: str - date in string format + :param str val: - date in string format :return: datetime.datetime """ try: - return datetime.date(*map(lambda x: int(x), s.split("-"))) - except Exception as e: + return datetime.date(*map(lambda x: int(x), val.split("-"))) + except Exception as err: return "" try: page = int(request.POST.get('page', 1)) if page < 1: page = 1 - except Exception as e: + except Exception as err: page = 1 users = [] @@ -100,7 +107,7 @@ def _get_date_from_string(s): users = filter_form.cleaned_data['users'] content_types = filter_form.cleaned_data['content_types'] else: - pass # Anything to do here? + pass # Anything to do here? else: filter_form = FilterForm() @@ -115,10 +122,10 @@ def _get_date_from_string(s): end_date = _get_date_from_string(request.POST.get('end_date')) if start_date: - log_entries = log_entries.filter(action_time__gte=start_date) # TODO + log_entries = log_entries.filter(action_time__gte=start_date) # TODO if end_date: - log_entries = log_entries.filter(action_time__lte=end_date) # TODO + log_entries = log_entries.filter(action_time__lte=end_date) # TODO # If users given, filtering by users if users: @@ -134,7 +141,7 @@ def _get_date_from_string(s): if log_entries: last_date = date_format( log_entries[len(log_entries) - 1].action_time, "Y-m-d" - ) + ) else: last_date = request.POST.get('last_date', None) @@ -147,16 +154,23 @@ def _get_date_from_string(s): 'page': page, 'last_date': request.POST.get('last_date', None), 'SINGLE_LOG_ENTRY_DATE_FORMAT': SINGLE_LOG_ENTRY_DATE_FORMAT, - 'LOG_ENTRIES_DAY_HEADINGS_DATE_FORMAT': \ + 'LOG_ENTRIES_DAY_HEADINGS_DATE_FORMAT': LOG_ENTRIES_DAY_HEADINGS_DATE_FORMAT } # Rendering HTML for an AJAX driven request - html = render_to_string( - template_name_ajax, - context, - context_instance=RequestContext(request) - ) + if versions.DJANGO_GTE_1_10: + html = render_to_string( + template_name_ajax, + context, + request=request + ) + else: + html = render_to_string( + template_name_ajax, + context, + context_instance=RequestContext(request) + ) # Context to send back to user in a JSON response context = { @@ -178,11 +192,16 @@ def _get_date_from_string(s): 'content_types': [int(ct) for ct in content_types], 'filter_form': filter_form, 'SINGLE_LOG_ENTRY_DATE_FORMAT': SINGLE_LOG_ENTRY_DATE_FORMAT, - 'LOG_ENTRIES_DAY_HEADINGS_DATE_FORMAT': \ + 'LOG_ENTRIES_DAY_HEADINGS_DATE_FORMAT': LOG_ENTRIES_DAY_HEADINGS_DATE_FORMAT, - 'title': _("Timeline") # For template breadcrumbs, etc. + 'title': _("Timeline") # For template breadcrumbs, etc. } - return render_to_response( - template_name, context, context_instance=RequestContext(request) + if versions.DJANGO_GTE_1_10: + return render(request, template_name, context) + else: + return render_to_response( + template_name, + context, + context_instance=RequestContext(request) ) diff --git a/tox.ini b/tox.ini index 416a560..3b270a8 100644 --- a/tox.ini +++ b/tox.ini @@ -1,44 +1,21 @@ [tox] -envlist = py26_django15,py27_django15,py33_django15,py26_django16,py27_django16,py33_django16 +envlist = + py{27}-{django14,django15,django16,django17} + py{27,34,35}-{django18,django19,django110} -[testenv:py26_django15] -deps=unittest2 +[testenv:py] +envlogdir= + examples/logs/ + examples/db/ + examples/tmp/ +passenv = * +deps = + django14: -r{toxinidir}/examples/requirements/django_1_4.txt + django15: -r{toxinidir}/examples/requirements/django_1_5.txt + django16: -r{toxinidir}/examples/requirements/django_1_6.txt + django17: -r{toxinidir}/examples/requirements/django_1_7.txt + django18: -r{toxinidir}/examples/requirements/django_1_8.txt + django19: -r{toxinidir}/examples/requirements/django_1_9.txt + django110: -r{toxinidir}/examples/requirements/django_1_10.txt commands= - pip install -r example/requirements.txt - {envpython} example/example/manage.py test admin_timeline - -[testenv:py27_django15] -deps=unittest2 -commands= - pip install -r example/requirements.txt - {envpython} example/example/manage.py test admin_timeline - -[testenv:py33_django15] -basepython=/opt/python3.3/bin/python3.3 -commands= - pip install -r example/requirements.txt - {envpython} example/example/manage.py test admin_timeline - -[testenv:py26_django16] -deps=unittest2 -commands= - pip install -r example/requirements.txt - pip uninstall Django -y - pip install Django==1.6 - {envpython} example/example/manage.py test admin_timeline - -[testenv:py27_django16] -deps=unittest2 -commands= - pip install -r example/requirements.txt - pip uninstall Django -y - pip install Django==1.6 - {envpython} example/example/manage.py test admin_timeline - -[testenv:py33_django16] -basepython=/opt/python3.3/bin/python3.3 -commands= - pip install -r example/requirements.txt - pip uninstall Django -y - pip install Django==1.6 - {envpython} example/example/manage.py test admin_timeline \ No newline at end of file + {envpython} runtests.py