Skip to content

Commit

Permalink
Merge branch 'develop' into feature/225-drag-drop-listview
Browse files Browse the repository at this point in the history
# Conflicts:
#	arctic/templates/arctic/partials/base_data_table.html
#	example/articles/views.py
  • Loading branch information
mmarcos committed Dec 12, 2017
2 parents b55423c + 352b4ed commit c054bcf
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 20 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
Bas Koopmans (@baskoopmans)
Bart van der Waerden (@BartvdWaerden)
David Esteves (@David-Esteves)
Diego Costa (@dgbc)
Johnny Mijnhout (@johnnymijnhout)
Jose Moreira (@zemanel)
Leen Kievit (@lkievit)
Expand Down
71 changes: 56 additions & 15 deletions arctic/generics.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@
from django.utils.translation import get_language
from django.views import generic as base

from .mixins import (FormMediaMixin, FormMixin, ListMixin, RoleAuthentication,
SuccessMessageMixin)
from .mixins import (FormMediaMixin, FormMixin, LinksMixin, ListMixin,
RoleAuthentication, SuccessMessageMixin)
from .paginator import IndefinitePaginator
from .utils import (arctic_setting, find_attribute, get_field_class,
find_field_meta, menu, reverse_url, view_from_url)
find_field_meta, get_attribute, menu, view_from_url)


class View(RoleAuthentication, base.View):
Expand Down Expand Up @@ -254,7 +254,7 @@ class TemplateView(View, base.TemplateView):
pass


class DetailView(View, base.DetailView):
class DetailView(View, LinksMixin, base.DetailView):
"""
Custom detail view.
"""
Expand All @@ -278,6 +278,7 @@ def get_fields(self, obj):
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
context['fields'] = self.get_fields(context['object'])
context['links'] = self.get_links()
return context


Expand Down Expand Up @@ -344,6 +345,24 @@ def get_object_list(self):

return self.object_list

def _reverse_field_link(self, url, obj):
if type(url) in (list, tuple):
named_url = url[0]
args = []
for arg in url[1:]:
args.append(find_attribute(obj, arg))
else:
named_url = url
args = [get_attribute(obj, self.primary_key)]

# Instead of giving NoReverseMatch exception
# its more desirable, for field_links in listviews
# to just ignore the link.
if None in args:
return ''

return reverse(named_url, args=args)

def get_list_header(self):
"""
Creates a list of dictionaries with the field names, labels,
Expand Down Expand Up @@ -392,6 +411,7 @@ def get_list_header(self):

def get_list_items(self, objects): # noqa: C901
self.has_action_links = False
has_confirm_links = hasattr(self, 'confirm_links')
items = []
if not self.get_fields():
for obj in objects:
Expand Down Expand Up @@ -424,11 +444,10 @@ def get_list_items(self, objects): # noqa: C901
embeded_list = embeded_list[:-1] + ['...']
field['value'] = embeded_list
if field_name in field_links.keys():
try:
field['url'] = reverse_url(
field_links[field_name], obj, self.primary_key)
except NoReverseMatch:
pass
field['url'] = self._reverse_field_link(
field_links[field_name], obj)
self.add_confirm_link(has_confirm_links, field,
field_links[field_name])
if field_name in field_classes:
field['class'] = field_classes[field_name]
row.append(field)
Expand All @@ -444,6 +463,10 @@ def get_list_items(self, objects): # noqa: C901
items.append(row)
return items

def add_confirm_link(self, has_confirm_link, field, field_url_name):
if has_confirm_link and field_url_name in self.confirm_links:
field['confirm'] = self.confirm_links[field_url_name]

def get_field_value(self, field_name, obj):
# first try to find a virtual field
virtual_field_name = "get_{}_field".format(field_name)
Expand Down Expand Up @@ -616,11 +639,8 @@ def get_list_items(self, objects):
field = {'field': field_name, 'type': 'field'}
field['value'] = self.get_field_value(field_name, obj)
if field_name in field_links.keys():
try:
field['url'] = reverse_url(
field_links[field_name], obj, self.primary_key)
except NoReverseMatch:
pass
field['url'] = self._reverse_field_link(
field_links[field_name], obj, self.primary_key)
if field_name in field_classes:
field['class'] = field_classes[field_name]
row.append(field)
Expand Down Expand Up @@ -669,6 +689,24 @@ def paginate_dataset(self, dataset, page_size):
'message': str(e)
})

def _reverse_field_link(self, url, obj):
if type(url) in (list, tuple):
named_url = url[0]
args = []
for arg in url[1:]:
args.append(obj[arg])
else:
named_url = url
args = [obj[self.primary_key]]

# Instead of giving NoReverseMatch exception
# its more desirable, for field_links in listviews
# to just ignore the link.
if None in args:
return ''

return reverse(named_url, args=args)


class CreateView(FormMediaMixin, View, SuccessMessageMixin,
FormMixin, extra_views.CreateWithInlinesView):
Expand All @@ -688,10 +726,13 @@ def get_context_data(self, **kwargs):


class UpdateView(FormMediaMixin, SuccessMessageMixin, FormMixin, View,
extra_views.UpdateWithInlinesView):
LinksMixin, extra_views.UpdateWithInlinesView):
template_name = 'arctic/base_create_update.html'
success_message = _('%(object)s was updated successfully')

links = None # Optional links such as list of linked items
readonly_fields = None # Optional list of readonly fields

def get_page_title(self):
if not self.page_title:
return _("Edit %s") % self.model._meta.verbose_name
Expand Down
29 changes: 27 additions & 2 deletions arctic/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,26 @@ def get_success_message(self, cleaned_data):
)


class LinksMixin(object):
"""
Adding links to view, to be resolved with 'arctic_url' template tag
"""
def get_links(self):
if not self.links:
return None
else:
allowed_links = []
for link in self.links:

# check permission based on named_url
if not view_from_url(link[1]).\
has_permission(self.request.user):
continue

allowed_links.append(link)
return allowed_links


class FormMixin(object):
"""
Adding customizable fields to view. Using the 12-grid system, you
Expand Down Expand Up @@ -454,13 +474,18 @@ def get_field_classes(self):

def _get_field_actions(self, obj):
field_actions = self.get_action_links()
has_confirm_links = hasattr(self, 'confirm_links')
if field_actions:
actions = []
for field_action in field_actions:
actions.append({'label': field_action['label'],
'icon': field_action['icon'],
'url': reverse_url(field_action['url'],
obj, self.primary_key)})
'url': self._reverse_field_link(
field_action['url'], obj)})
field_url_name = field_action['url']
if has_confirm_links and field_url_name in self.confirm_links:
actions[0].update({'confirm':
self.confirm_links[field_url_name]})
return {'type': 'actions', 'actions': actions}
return None

Expand Down
2 changes: 1 addition & 1 deletion arctic/static/arctic/dist/assets/css/arctic.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion arctic/static/arctic/dist/assets/js/app.js

Large diffs are not rendered by default.

48 changes: 47 additions & 1 deletion arctic/templates/arctic/partials/base_data_table.html
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,32 @@ <h4 class="arctic-card__title">
{% for row in list_items %}
<tr>
{% for column in row %}
{% if column.confirm %}
<div class="modal fade" id="modal{{ column.value }}" tabindex="-1" role="dialog" aria-labelledby="modal{{ column.value }}Label" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalLabel">Confirm your action</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
{{ column.confirm|lookup:'message' }}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ column.confirm|lookup:'cancel' }}</button>
<a class="btn btn-primary" href="{{ column.url }}" onclick="$('#modal{{ column.value }}').modal('hide')">{{ column.confirm|lookup:'yes' }}</a>
</div>
</div>
</div>
</div>
{% endif %}
{% if column.type == 'field' %}
<td {% if column.class %}class="{{ column.class }}"{%endif%}>
{% if column.url %}
{% if column.confirm %}
<a data-toggle="modal" data-target="#modal{{ column.value }}">
{% elif column.url %}
<a href="{{ column.url }}"{% if list_items.0.0.type == 'sorting' %}style="display:block;"{% endif %}>
{% endif %}
{% if column.value|typename != 'list' %}
Expand All @@ -122,7 +145,30 @@ <h4 class="arctic-card__title">
<td>
<div class="list-actions">
{% for link in column.actions %}
{% if link.confirm %}
<div class="modal fade" id="modal{{ link.label }}" tabindex="-1" role="dialog" aria-labelledby="modal{{ link.label }}Label" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Confirm your action</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
{{ link.confirm|lookup:'message' }}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ link.confirm|lookup:'cancel' }}</button>
<a class="btn btn-primary" href="{{ link.url }}" onclick="$('#modal{{ link.value }}').modal('hide')">{{ link.confirm|lookup:'yes' }}</a>
</div>
</div>
</div>
</div>
<a data-toggle="modal" class="action-{{ link.label }} btn btn-secondary btn-sm show-on-hover" data-target="#modal{{ link.label }}" title=" 2{{ link.label|capfirst }}">
{% elif not link.confirm %}
<a href="{{ link.url }}" class="action-{{ link.label }} btn btn-secondary btn-sm show-on-hover" title="{{ link.label|capfirst }}">
{% endif %}
{% if link.icon %}
<i class="fa {{ link.icon }} fa-lg"></i>
{% else %}
Expand Down
36 changes: 36 additions & 0 deletions docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,42 @@ table data.
default is fa-wrench. an icon displayed for the dropdown of multiple tool
links or, if only one tool link set, it would be use as default icon.

### `confirm_links`

dictionary as `{'url_field': {'message': 'Would you like to continue?', 'yes': 'Yes', 'cancel': 'No'}}'` which wraps every `url_field` displayed on a `ListView` with a confirmation dialog.

### `advanced_search_form`

arctic provides a search endpoint via `advanced_search_form` which accepts a regular `django.forms.Form`.
A basic implementation of an advanced search form must implement the `get_search_filter()`.
Example:

class ExampleListView(ListView):
...
advanced_search_form = AdvancedSearchForm
...
.
.
from django.db.models import Q

class AdvancedSearchForm(Form):
"""
Basic implementation of arctic's advanced search form class
"""
modified_on = forms.DateTimeField(required=False,
widget=forms.DateInput(attrs={'js-datepicker': ''}))
created_on = forms.DateTimeField(required=False,
widget=forms.DateInput(attrs={'js-datepicker': ''}))
def get_search_filter(self):
conditions = Q()
modified_on_value = self.stored_data.get('modified_on')
if modified_on_value:
conditions &= Q(modified_on__gte=modified_on_value)
created_on_value = self.stored_data.get('created_on')
if created_on_value:
conditions &= Q(created_on__gte=created_on_value)
return conditions

## DataListView

Expand Down
12 changes: 12 additions & 0 deletions example/articles/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,18 @@ class CategoryListView(ListView):
]
permission_required = 'view_category'
sorting_field = 'order'
action_links = [
('delete', 'articles:category-delete', 'fa-trash'),
]
confirm_links = {
'articles:category-delete': {
'message': _('Are you sure you want to delete this?'),
'yes': _('Yes'),
'cancel': _('No')},
'articles:category-detail': {
'message': _('Are you sure you want to proceed'),
'yes': _('Yes'),
'cancel': _('No')}}


class CategoryArticlesListView(ArticleListView):
Expand Down

0 comments on commit c054bcf

Please sign in to comment.