mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-11-03 03:16:10 -06:00 
			
		
		
		
	A nicer look for the documents listing
This change includes a filthy hack around how Django handles change_list_results.html -- I'm not thrilled with it, but it's as elegant as I could come up with. I'm happy to field alternative ideas. More details can be found in `documents/templatetags/hacks.py` Specifically, this merge includes a significant facelift to the documents listing page, moving away from the tabular layout and toward a tileset look. I tried fiddling with the colours, but I just don't have any skills in that area, so we're all stuck with Django'd default colours until someone with an eye for colour can submit a better CSS.
This commit is contained in:
		@@ -1,6 +1,15 @@
 | 
			
		||||
Changelog
 | 
			
		||||
#########
 | 
			
		||||
 | 
			
		||||
* 0.3.5
 | 
			
		||||
  * A serious facelift for the documents listing page wherein we drop the
 | 
			
		||||
    tabular layout in favour of a tiled interface.
 | 
			
		||||
  * Users can now configure the number of items per page.
 | 
			
		||||
  * Fix for `#171`_: Allow users to specify their own ``SECRET_KEY`` value.
 | 
			
		||||
  * Moved the dotenv loading to the top of settings.py
 | 
			
		||||
  * Fix for `#112`_: Added checks for binaries required for document
 | 
			
		||||
    consumption.
 | 
			
		||||
 | 
			
		||||
* 0.3.4
 | 
			
		||||
  * Removal of django-suit due to a licensing conflict I bumped into in 0.3.3.
 | 
			
		||||
    Note that you *can* use Django Suit with Paperless, but only in a
 | 
			
		||||
@@ -42,7 +51,8 @@ Changelog
 | 
			
		||||
    ``paperless.conf``.
 | 
			
		||||
  * `#148`_: The database location (sqlite) is now a variable you can set in
 | 
			
		||||
    ``paperless.conf``.
 | 
			
		||||
  * `#146`_: Fixed a bug that allowed unauthorised access to the `/fetch` URL.
 | 
			
		||||
  * `#146`_: Fixed a bug that allowed unauthorised access to the ``/fetch``
 | 
			
		||||
    URL.
 | 
			
		||||
  * `#131`_: Document files are now automatically removed from disk when
 | 
			
		||||
    they're deleted in Paperless.
 | 
			
		||||
  * `#121`_: Fixed a bug where Paperless wasn't setting document creation time
 | 
			
		||||
@@ -168,10 +178,12 @@ Changelog
 | 
			
		||||
.. _#89: https://github.com/danielquinn/paperless/issues/89
 | 
			
		||||
.. _#94: https://github.com/danielquinn/paperless/issues/94
 | 
			
		||||
.. _#98: https://github.com/danielquinn/paperless/issues/98
 | 
			
		||||
.. _#112: https://github.com/danielquinn/paperless/issues/112
 | 
			
		||||
.. _#121: https://github.com/danielquinn/paperless/issues/121
 | 
			
		||||
.. _#131: https://github.com/danielquinn/paperless/issues/131
 | 
			
		||||
.. _#146: https://github.com/danielquinn/paperless/issues/146
 | 
			
		||||
.. _#148: https://github.com/danielquinn/paperless/pull/148
 | 
			
		||||
.. _#150: https://github.com/danielquinn/paperless/pull/150
 | 
			
		||||
.. _#171: https://github.com/danielquinn/paperless/issues/171
 | 
			
		||||
.. _#172: https://github.com/danielquinn/paperless/issues/172
 | 
			
		||||
.. _#179: https://github.com/danielquinn/paperless/pull/179
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +1,18 @@
 | 
			
		||||
Django==1.10.4
 | 
			
		||||
Django==1.10.5
 | 
			
		||||
Pillow>=3.1.1
 | 
			
		||||
django-crispy-forms>=1.6.0
 | 
			
		||||
django-extensions>=1.6.1
 | 
			
		||||
django-crispy-forms>=1.6.1
 | 
			
		||||
django-extensions>=1.7.6
 | 
			
		||||
django-filter>=1.0
 | 
			
		||||
djangorestframework>=3.4.4
 | 
			
		||||
django-flat-responsive>=1.2.0
 | 
			
		||||
djangorestframework>=3.5.3
 | 
			
		||||
filemagic>=1.6
 | 
			
		||||
langdetect>=1.0.5
 | 
			
		||||
pyocr>=0.3.1
 | 
			
		||||
python-dateutil>=2.4.2
 | 
			
		||||
python-dotenv>=0.3.0
 | 
			
		||||
python-gnupg>=0.3.8
 | 
			
		||||
pytz>=2015.7
 | 
			
		||||
langdetect>=1.0.7
 | 
			
		||||
pyocr>=0.4.6
 | 
			
		||||
python-dateutil>=2.6.0
 | 
			
		||||
python-dotenv>=0.6.2
 | 
			
		||||
python-gnupg>=0.3.9
 | 
			
		||||
pytz>=2016.10
 | 
			
		||||
gunicorn==19.6.0
 | 
			
		||||
django-flat-responsive==1.2.0
 | 
			
		||||
 | 
			
		||||
# For the tests
 | 
			
		||||
pytest
 | 
			
		||||
 
 | 
			
		||||
@@ -58,7 +58,7 @@ class DocumentAdmin(CommonAdmin):
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    search_fields = ("correspondent__name", "title", "content")
 | 
			
		||||
    list_display = ("created", "title", "thumbnail", "correspondent", "tags_")
 | 
			
		||||
    list_display = ("title", "created", "thumbnail", "correspondent", "tags_")
 | 
			
		||||
    list_filter = ("tags", "correspondent", MonthListFilter)
 | 
			
		||||
    ordering = ["-created", "correspondent"]
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								src/documents/templates/admin/change_list_results.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/documents/templates/admin/change_list_results.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
{% load hacks %}
 | 
			
		||||
 | 
			
		||||
{# See documents.templatetags.hacks.change_list_results for an explanation #}
 | 
			
		||||
 | 
			
		||||
{% change_list_results %}
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1,167 @@
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
  .grid *, .grid *:after, .grid *:before {
 | 
			
		||||
    -webkit-box-sizing: border-box;
 | 
			
		||||
    -moz-box-sizing: border-box;
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
  }
 | 
			
		||||
  .box {
 | 
			
		||||
    width: 12.5%;
 | 
			
		||||
    padding: 1em;
 | 
			
		||||
    float: left;
 | 
			
		||||
    opacity: 0.7;
 | 
			
		||||
    transition: all 0.5s;
 | 
			
		||||
  }
 | 
			
		||||
  .box:hover {
 | 
			
		||||
    opacity: 1;
 | 
			
		||||
    transition: all 0.5s;
 | 
			
		||||
  }
 | 
			
		||||
  .box:last-of-type {
 | 
			
		||||
    padding-right: 0;
 | 
			
		||||
  }
 | 
			
		||||
  .result {
 | 
			
		||||
    border: 1px solid #cccccc;
 | 
			
		||||
    border-radius: 2%;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    height: 300px;
 | 
			
		||||
  }
 | 
			
		||||
  .result .header {
 | 
			
		||||
    padding: 5px;
 | 
			
		||||
    background-color: #79AEC8;
 | 
			
		||||
    height: 6em;
 | 
			
		||||
  }
 | 
			
		||||
  .result .header .checkbox {
 | 
			
		||||
    margin-right: 5px;
 | 
			
		||||
  }
 | 
			
		||||
  .result .header .checkbox{
 | 
			
		||||
    width: 5%;
 | 
			
		||||
    float: left;
 | 
			
		||||
  }
 | 
			
		||||
  .result .header .info {
 | 
			
		||||
    width: 90%;
 | 
			
		||||
    float: left;
 | 
			
		||||
  }
 | 
			
		||||
  .result .header a,
 | 
			
		||||
  .result a.tag {
 | 
			
		||||
    color: #ffffff;
 | 
			
		||||
  }
 | 
			
		||||
  .result .date {
 | 
			
		||||
    padding: 5px;
 | 
			
		||||
  }
 | 
			
		||||
  .result .tags {
 | 
			
		||||
    float: left;
 | 
			
		||||
  }
 | 
			
		||||
  .result .tags a.tag {
 | 
			
		||||
    padding: 2px 5px;
 | 
			
		||||
    border-radius: 2px;
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    margin: 2px;
 | 
			
		||||
  }
 | 
			
		||||
  .result .date {
 | 
			
		||||
    float: right;
 | 
			
		||||
    color: #cccccc;
 | 
			
		||||
  }
 | 
			
		||||
  .result .image img {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .grid {
 | 
			
		||||
    margin-right: 260px;
 | 
			
		||||
  }
 | 
			
		||||
  .grid:after {
 | 
			
		||||
    content: "";
 | 
			
		||||
    display: table;
 | 
			
		||||
    clear: both;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @media (max-width: 1600px) {
 | 
			
		||||
    .box {
 | 
			
		||||
      width: 25%
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @media (max-width: 991px) {
 | 
			
		||||
    .grid {
 | 
			
		||||
      margin-right: 220px;
 | 
			
		||||
    }
 | 
			
		||||
    .box {
 | 
			
		||||
      width: 50%
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @media (max-width: 767px) {
 | 
			
		||||
    .grid {
 | 
			
		||||
      margin-right: 0;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @media (max-width: 500px) {
 | 
			
		||||
    .box {
 | 
			
		||||
      width: 100%
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{# This is just copypasta from the parent change_list_results.html file #}
 | 
			
		||||
<table id="result_list">
 | 
			
		||||
<thead>
 | 
			
		||||
<tr>
 | 
			
		||||
{% for header in result_headers %}
 | 
			
		||||
<th scope="col" {{ header.class_attrib }}>
 | 
			
		||||
   {% if header.sortable %}
 | 
			
		||||
     {% if header.sort_priority > 0 %}
 | 
			
		||||
       <div class="sortoptions">
 | 
			
		||||
         <a class="sortremove" href="{{ header.url_remove }}" title="{% trans "Remove from sorting" %}"></a>
 | 
			
		||||
         {% if num_sorted_fields > 1 %}<span class="sortpriority" title="{% blocktrans with priority_number=header.sort_priority %}Sorting priority: {{ priority_number }}{% endblocktrans %}">{{ header.sort_priority }}</span>{% endif %}
 | 
			
		||||
         <a href="{{ header.url_toggle }}" class="toggle {% if header.ascending %}ascending{% else %}descending{% endif %}" title="{% trans "Toggle sorting" %}"></a>
 | 
			
		||||
       </div>
 | 
			
		||||
     {% endif %}
 | 
			
		||||
   {% endif %}
 | 
			
		||||
   <div class="text">{% if header.sortable %}<a href="{{ header.url_primary }}">{{ header.text|capfirst }}</a>{% else %}<span>{{ header.text|capfirst }}</span>{% endif %}</div>
 | 
			
		||||
   <div class="clear"></div>
 | 
			
		||||
</th>{% endfor %}
 | 
			
		||||
</tr>
 | 
			
		||||
</thead>
 | 
			
		||||
</table>
 | 
			
		||||
{# /copypasta #}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<div class="grid">
 | 
			
		||||
  {% for result in results %}
 | 
			
		||||
    {# 0: Checkbox #}
 | 
			
		||||
    {# 1: Title #}
 | 
			
		||||
    {# 2: Date #}
 | 
			
		||||
    {# 3: Image #}
 | 
			
		||||
    {# 4: Correspondent #}
 | 
			
		||||
    {# 5: Tags #}
 | 
			
		||||
    <div class="box">
 | 
			
		||||
      <div class="result">
 | 
			
		||||
        <div class="header">
 | 
			
		||||
          <div class="checkbox">{{ result.0 }}</div>
 | 
			
		||||
          <div class="info">
 | 
			
		||||
            {{ result.4 }}<br />
 | 
			
		||||
            {{ result.1 }}
 | 
			
		||||
          </div>
 | 
			
		||||
          <div style="clear: both;"></div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="tags">{{ result.5 }}</div>
 | 
			
		||||
        <div class="date">{{ result.2 }}</div>
 | 
			
		||||
        <div style="clear: both;"></div>
 | 
			
		||||
        <div class="image">{{ result.3 }}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  {% endfor %}
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
  // We nee to re-build the select-all functionality as the old logic pointed
 | 
			
		||||
  // to a table and we're using divs now.
 | 
			
		||||
  django.jQuery("#action-toggle").on("change", function(){
 | 
			
		||||
    django.jQuery(".grid .box .result .checkbox input")
 | 
			
		||||
      .prop("checked", this.checked);
 | 
			
		||||
  });
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										0
									
								
								src/documents/templatetags/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								src/documents/templatetags/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										41
									
								
								src/documents/templatetags/hacks.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/documents/templatetags/hacks.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
from django.contrib import admin
 | 
			
		||||
from django.template import Library
 | 
			
		||||
from django.template.loader import get_template
 | 
			
		||||
 | 
			
		||||
from ..models import Document
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
register = Library()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.simple_tag(takes_context=True)
 | 
			
		||||
def change_list_results(context):
 | 
			
		||||
    """
 | 
			
		||||
    Django has a lot of places where you can override defaults, but
 | 
			
		||||
    unfortunately, `change_list_results.html` is not one of them.  In fact,
 | 
			
		||||
    it's a downright pain in the ass to override this file on a per-model basis
 | 
			
		||||
    and this is the cleanest way I could come up with.
 | 
			
		||||
 | 
			
		||||
    Basically all we've done here is defined `change_list_results.html` in an
 | 
			
		||||
    `admin` directory which globally overrides that file for *every* model.
 | 
			
		||||
    That template however simply loads this templatetag which determines
 | 
			
		||||
    whether we're currently looking at a `Document` listing or something else
 | 
			
		||||
    and loads the appropriate file in each case.
 | 
			
		||||
 | 
			
		||||
    Better work arounds for this are welcome as I hate this myself, but at the
 | 
			
		||||
    moment, it's all I could come up with.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    path = os.path.join(
 | 
			
		||||
        os.path.dirname(admin.__file__),
 | 
			
		||||
        "templates",
 | 
			
		||||
        "admin",
 | 
			
		||||
        "change_list_results.html"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    if context["cl"].model == Document:
 | 
			
		||||
        path = "admin/documents/document/change_list_results.html"
 | 
			
		||||
 | 
			
		||||
    return get_template(path).render(context)
 | 
			
		||||
@@ -52,18 +52,19 @@ if _allowed_hosts:
 | 
			
		||||
 | 
			
		||||
INSTALLED_APPS = [
 | 
			
		||||
 | 
			
		||||
    'flat_responsive',
 | 
			
		||||
    'django.contrib.admin',
 | 
			
		||||
    'django.contrib.auth',
 | 
			
		||||
    'django.contrib.contenttypes',
 | 
			
		||||
    'django.contrib.sessions',
 | 
			
		||||
    'django.contrib.messages',
 | 
			
		||||
    'django.contrib.staticfiles',
 | 
			
		||||
    "django.contrib.auth",
 | 
			
		||||
    "django.contrib.contenttypes",
 | 
			
		||||
    "django.contrib.sessions",
 | 
			
		||||
    "django.contrib.messages",
 | 
			
		||||
    "django.contrib.staticfiles",
 | 
			
		||||
 | 
			
		||||
    "django_extensions",
 | 
			
		||||
 | 
			
		||||
    "documents.apps.DocumentsConfig",
 | 
			
		||||
 | 
			
		||||
    "flat_responsive",
 | 
			
		||||
    "django.contrib.admin",
 | 
			
		||||
 | 
			
		||||
    "rest_framework",
 | 
			
		||||
    "crispy_forms",
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
__version__ = (0, 3, 4)
 | 
			
		||||
__version__ = (0, 3, 5)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user