Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add filters to lists in dasboards #1299

Merged
merged 108 commits into from
Nov 30, 2017
Merged

Conversation

koradon
Copy link
Contributor

@koradon koradon commented Nov 14, 2017

Add filter capability for lists in dashboard.

  • sorting the list by clicking on the column names
  • filters corresponding with the displayed columns
  • UI should be compliant with Material Design. These designs include filter icon in the table header.
  • combine fields name, last name and email to one filter
  • add fancy datepicker for filters with dates and dates ranges

Fixes #256
Fixes #1387

@koradon koradon added this to the 2017.11 milestone Nov 14, 2017
@koradon koradon self-assigned this Nov 14, 2017
@codecov-io
Copy link

codecov-io commented Nov 14, 2017

Codecov Report

Merging #1299 into master will increase coverage by 0.01%.
The diff coverage is 87.3%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1299      +/-   ##
==========================================
+ Coverage   81.97%   81.98%   +0.01%     
==========================================
  Files         115      123       +8     
  Lines        5598     5746     +148     
  Branches      644      667      +23     
==========================================
+ Hits         4589     4711     +122     
- Misses        850      878      +28     
+ Partials      159      157       -2
Impacted Files Coverage Δ
saleor/dashboard/order/forms.py 67.98% <ø> (-0.28%) ⬇️
saleor/dashboard/discount/views.py 69.62% <100%> (+1.19%) ⬆️
saleor/dashboard/group/views.py 100% <100%> (ø) ⬆️
saleor/dashboard/group/filters.py 100% <100%> (ø)
saleor/core/templatetags/shop.py 94.87% <100%> (+4.87%) ⬆️
saleor/core/templatetags/materializecss.py 89.47% <100%> (+1.71%) ⬆️
saleor/dashboard/customer/filters.py 100% <100%> (ø)
saleor/dashboard/category/views.py 57.69% <100%> (+1.11%) ⬆️
saleor/dashboard/product/views.py 70.07% <100%> (+0.46%) ⬆️
saleor/dashboard/product/filters.py 100% <100%> (ø)
... and 30 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update a7922c5...c44ad28. Read the comment docs.


def get_sort_by_choices(filter):
return [(choice[0], choice[1].lower()) for choice in
filter.filters['sort_by'].field.choices[1::2]]
Copy link
Contributor

@Pacu2 Pacu2 Nov 17, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should not name your variables after built-in functions, please rename filter to something else

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any suggestions? I wanted to change it but...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could name it filter_set after the class from django-filters.



def get_now_sorted_by(filter, fields, default_sort='name'):
sort_by = filter.form.cleaned_data.get('sort_by')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

else:
orders = orders_all.all()
orders = (Order.objects.prefetch_related(
'groups', 'payments', 'groups__items', 'user').order_by('-pk'))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you prefetch some_field__somefields_field then some_field will be automatically prefetched.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, this is not my code but i will change it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, I only added order_by and delete if :)
But this is exactly same code from invoice ;)

<div class="card-content">
<div class="col s12">
<span class="card-title">{% trans 'Filters' context 'Dashboard card title' %}</span>
<span>{% trans 'Total records found' context 'Dashboard filter' %}: {{ filter.qs|length }}</span>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use count here? I think passing it from the backend would be better, as usually, we are trying to avoid templates firing up additional queries.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will check if this triggers query.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does. In case of a large dataset this would be really slow. Actually we don't display the number of items in queryset in the list views. Maybe it would be better to add it consistently to all lists in a different PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm i don't think that another PR is required. I already have to do this so why not now?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we add it to every list then it's fine in this one.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is knowing the precise number of results useful? When is it useful? When is it not? It would be nice to have some use cases before we add something to all list views.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the reason @patrys just mentioned I would split it out to another issue/PR.

Copy link
Member

@mirekm mirekm Nov 17, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the other hand we need a visual feedback that there's some filtering being applied and number of records found would partially serve that purpose.

<div class="row">
{% for field in filter.form %}
<div class="col s12">
{% if field.name == 'sort_by' %}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a fan of, I think rendering those fields separately would be a more error-proof option.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those fields are taken from many filters, adding every possible field would create enormous if statement.

@@ -1,6 +1,7 @@
{% extends "dashboard/base.html" %}
{% load i18n %}
{% load prices_i18n %}
{% load shop %}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're using only one function, it would be more convenient to use {% load function from shop %}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do. But I think we need PR which will make those changes for every template for every templatetag library

@@ -289,7 +289,7 @@ def get_host():

TEST_RUNNER = ''

ALLOWED_HOSTS = get_list(os.environ.get('ALLOWED_HOSTS', 'localhost'))
ALLOWED_HOSTS = ['*']
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like local settings.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooops

@koradon
Copy link
Contributor Author

koradon commented Nov 23, 2017

@dominik-zeglen RangeWidget and DateRangeWidget have some issues with 'clicking aea'. Could you investigate it?

@koradon koradon force-pushed the feature/dashboard_filters branch 2 times, most recently from 5ce3b88 to 4597cbd Compare November 27, 2017 12:15
@maarcingebala
Copy link
Member

maarcingebala commented Nov 27, 2017

@dominik-zeglen @koradon I've reviewed the UI and have a few remarks:

  • sort icons appear only in the product list view
  • I think the "filter" button should be flat; most buttons are flat e.g. in product details page or order details
  • there is an issue with opening the datepicker widget for the date or date-range filters
  • the text color in table headers should be always the same (regardless of whether the sorting is allowed or not), but we should show maybe some underline when hovering on headers of sortable columns?
  • we could show the "clear" button only when filters are enabled
  • include currency in price filters (see product edit form for reference)
  • add filtering by name in stock locations list view

def handle_nullboolean(field):
value = yesno(
field.value(),
pgettext_lazy('Possible values of boolean filter', 'yes,no,all'))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

descriptive 👍

from django.template.defaultfilters import yesno
from django.utils.translation import pgettext_lazy

PATTERN = '%s: %s'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PATTERN of ..?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chip formatting

@@ -0,0 +1,65 @@
from django.template.defaultfilters import yesno
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing unicode_literals import

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

label=pgettext_lazy('Staff list filter label', 'Groups'),
name='groups',
queryset=Group.objects.all())
is_active = ChoiceFilter(
Copy link
Contributor

@Pacu2 Pacu2 Nov 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use BooleanField here? I think it's more obvious than returning '1' or '0' as str

Copy link
Contributor Author

@koradon koradon Nov 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately BooleanFilter does not have 'empty_label' and 'choices' params. Sience we want a custom string 'All' if no option was chosen and a custom choices like 'Published'/'Not published' I had to use ChoiceFilter. And this is a recommended approach for this kind of customization.

choices=PUBLISHED_CHOICES,
empty_label=pgettext_lazy('Filter empty choice label', 'All'),
widget=forms.Select)
is_featured = ChoiceFilter(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as with is_active

request_get = request.GET.copy()
sort_by = request_get.get('sort_by')
arrow_src = ''
active = ''
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather expect a boolean variable is_active. If I'm correct below a CSS class is set as value of this variable which is not a good practice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, i wanted less logic in template

if field == sort_by[1:]:
arrow_src = static('/images/arrow_down_icon.svg')
active = 'active'
else:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When this case happens? Isn't setting of these values already covered in lines 41 and 42?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, some old code i guess. Deleted

from django.db.models import Q


def get_sort_by_choices(filter_set):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add docstrings to all of these functions.

request = context['request']
request_get = request.GET.copy()
sort_by = request_get.get('sort_by')
arrow_src = ''
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rename it to sorting_icon. If we changed from arrows to something else we won't have to update the variable names :)

Add filters.py to dashboard/product.
Update product_list view.
Update dashboard/product/list.html.
Add template dashboard/_filters.html.
Add include tag.
Move get_sort_by_choices and get_now_sorted_by to core.utils.filters.py.
Add filters to order list.
Change include path from relative to absolute.
Move _filters.html from dashboard to dashboard/includes.
Delete _status_filters.html.
Add sort_by links to order and customer lists.
Move include _filters before check if QuerySet exists.
@maarcingebala maarcingebala merged commit a16bad9 into master Nov 30, 2017
@maarcingebala maarcingebala deleted the feature/dashboard_filters branch November 30, 2017 14:19
"""Build a list of chips for NullBooleanField field."""
value = yesno(
field.value(),
pgettext_lazy('Possible values of boolean filter', 'yes,no,all'))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't do this. If that's the only way to pass data do yesno then please don't use yesno as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants