mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05: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
	 Daniel Quinn
					Daniel Quinn