Skip to content

Commit

Permalink
Add react.js-based paginated tables (#1940)
Browse files Browse the repository at this point in the history
  • Loading branch information
alukach authored Feb 5, 2018
1 parent 5ab7e0b commit fdb32e4
Show file tree
Hide file tree
Showing 29 changed files with 2,437 additions and 118 deletions.
4 changes: 4 additions & 0 deletions cadasta/core/static/babel-polyfill/6.26.0/polyfill.min.js

Large diffs are not rendered by default.

442 changes: 442 additions & 0 deletions cadasta/core/static/jsx/paginated-table.jsx

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions cadasta/core/static/moment.js/2.20.1/moment.min.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions cadasta/core/static/react@15/15.6.2/prop-types.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions cadasta/core/static/react@15/15.6.2/react-dom.min.js

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions cadasta/core/static/react@15/15.6.2/react.min.js

Large diffs are not rendered by default.

23 changes: 19 additions & 4 deletions cadasta/party/serializers.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,41 @@
"""Party Serializers."""

from django.utils.functional import cached_property
from django.utils.translation import ugettext as _
from rest_framework import serializers

from . import models
from core import serializers as core_serializers
from .choices import TENURE_RELATIONSHIP_TYPES
from spatial.serializers import SpatialUnitSerializer
from core.form_mixins import get_types
from spatial.serializers import SpatialUnitSerializer
from questionnaires.models import QuestionOption
from . import models
from .choices import TENURE_RELATIONSHIP_TYPES


class PartySerializer(core_serializers.JSONAttrsSerializer,
core_serializers.SanitizeFieldSerializer,
core_serializers.FieldSelectorSerializer,
serializers.ModelSerializer):
attrs_selector = 'type'
type_display = serializers.SerializerMethodField()

class Meta:
model = models.Party
fields = ('id', 'name', 'type', 'attributes', )
fields = ('id', 'name', 'type', 'attributes', 'type_display')
read_only_fields = ('id', )

@cached_property
def type_displays_translations(self):
project = self.context['project']
return dict(QuestionOption.objects.filter(
question__name='party_type',
question__questionnaire_id=project.current_questionnaire
).values_list('name', 'label_xlat'))

def get_type_display(self, instance):
return self.type_displays_translations.get(instance.type,
instance.get_type_display())

def create(self, validated_data):
project = self.context['project']
return models.Party.objects.create(
Expand Down
37 changes: 36 additions & 1 deletion cadasta/party/tests/test_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,47 @@
class PartySerializerTest(UserTestCase, TestCase):
def test_serialize_party(self):
party = PartyFactory.create()
serializer = serializers.PartySerializer(party)
proj = ProjectFactory.create()
serializer = serializers.PartySerializer(party, context={
'project': proj
})
serialized = serializer.data

assert serialized['id'] == party.id
assert serialized['name'] == party.name
assert serialized['type'] == party.type
assert serialized['type_display'] == party.get_type_display()
assert 'attributes' in serialized

def test_serialize_party_with_translations(self):
proj = ProjectFactory.create()

questionnaire = q_factories.QuestionnaireFactory.create(
project=proj)
question = q_factories.QuestionFactory.create(
type='S1',
name='party_type',
questionnaire=questionnaire)
q_factories.QuestionOptionFactory.create(
question=question,
name='IN',
label={'en': 'Individual', 'de': 'Individuell'})
q_factories.QuestionOptionFactory.create(
question=question,
name='GR',
label={'en': 'Group', 'de': 'Gruppe'})

party = PartyFactory.create(project=proj)
serializer = serializers.PartySerializer(party, context={
'project': proj
})
serialized = serializer.data

assert serialized['id'] == party.id
assert serialized['name'] == party.name
assert serialized['type'] == party.type
assert serialized['type_display'] == {'en': 'Individual',
'de': 'Individuell'}
assert 'attributes' in serialized

def test_create_party(self):
Expand Down
2 changes: 1 addition & 1 deletion cadasta/party/views/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class PartyList(APIPermissionRequiredMixin,
filters.SearchFilter, filters.OrderingFilter,)
filter_fields = ('name', 'type')
search_fields = ('name',)
ordering_fields = ('name',)
ordering_fields = ('name', 'type')
permission_required = {
'GET': 'party.list',
'POST': update_permissions('party.create')
Expand Down
21 changes: 0 additions & 21 deletions cadasta/party/views/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,6 @@ class PartiesList(LoginPermissionRequiredMixin,
template_name = 'party/party_list.html'
permission_required = 'party.list'
permission_denied_message = error_messages.PARTY_LIST
no_jsonattrs_in_queryset = True

def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
project = context['object']

if project.current_questionnaire:
try:
party_type = Question.objects.get(
name='party_type',
questionnaire_id=project.current_questionnaire)
party_opts = QuestionOption.objects.filter(question=party_type)
party_opts = dict(party_opts.values_list('name', 'label_xlat'))

for party in context['object_list']:
party.type_labels = template_xlang_labels(
party_opts.get(party.type))
except Question.DoesNotExist:
pass

return context


class PartiesAdd(LoginPermissionRequiredMixin,
Expand Down
5 changes: 0 additions & 5 deletions cadasta/party/views/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@ class PartyQuerySetMixin(ProjectMixin):
def get_queryset(self):
self.proj = self.get_project()
parties = self.proj.parties.all()
if (
hasattr(self, 'no_jsonattrs_in_queryset') and
self.no_jsonattrs_in_queryset
):
parties = parties.only('id', 'name', 'type', 'project')
return parties

def get_serializer_context(self, *args, **kwargs):
Expand Down
2 changes: 1 addition & 1 deletion cadasta/resources/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def file_name(self):

@property
def file_type(self):
return self.file_name.split('.')[-1]
return self.file_name.lower().split('.')[-1]

@property
def thumbnail(self):
Expand Down
13 changes: 11 additions & 2 deletions cadasta/resources/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,35 @@


class ContentObjectSerializer(serializers.ModelSerializer):
link_id = serializers.CharField(source='id')
id = serializers.CharField(source='object_id')
type = serializers.CharField(source='content_type.model')

class Meta:
model = ContentObject
fields = ('id', 'type')
fields = ('link_id', 'id', 'type')


class ResourceSerializer(SanitizeFieldSerializer, serializers.ModelSerializer):
file = S3Field()
links = ContentObjectSerializer(
many=True, required=False, source='content_objects')
contributor = serializers.SerializerMethodField()

class Meta:
model = Resource
fields = ('id', 'name', 'description', 'file', 'original_file',
'archived', 'mime_type', 'links')
'archived', 'mime_type', 'links',
'contributor', 'last_updated', 'file_type', 'thumbnail',)
read_only_fields = ('id', )
extra_kwargs = {'mime_type': {'required': False}}

def get_contributor(self, obj):
return {
'username': obj.contributor.username,
'full_name': obj.contributor.full_name
}

def is_valid(self, raise_exception=False):
data = self.initial_data
if 'id' in data:
Expand Down
27 changes: 15 additions & 12 deletions cadasta/resources/tests/test_views_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,22 +153,25 @@ def test_add_with_archived_project(self):
assert self.project.resources.count() == 3

def test_search_for_file(self):
file = self.get_file('/resources/tests/files/image.jpg', 'rb')
not_found = self.storage.save('resources/bild.jpg', file.read())
file.close()
prj = ProjectFactory.create()
ResourceFactory.create_from_kwargs([
{'content_object': prj, 'project': prj, 'file': self.file_name},
{'content_object': prj, 'project': prj, 'file': self.file_name},
{'content_object': prj, 'project': prj, 'file': not_found}
{
'content_object': prj,
'project': prj,
'original_file': 'my/image.png'
},
{
'content_object': prj,
'project': prj,
'original_file': 'foo/an_image_thing.jpeg'
},
{
'content_object': prj,
'project': prj,
'original_file': 'bar.png'
}
])

# self._get(prj.organization.slug,
# prj.slug,
# query='search=image',
# status=200,
# count=2)

response = self.request(
user=self.user,
url_kwargs={'organization': prj.organization.slug,
Expand Down
20 changes: 17 additions & 3 deletions cadasta/resources/views/api.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
from django.contrib.contenttypes.models import ContentType
from django.db.models import Exists, OuterRef
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import filters, generics
from tutelary.mixins import APIPermissionRequiredMixin

from core.mixins import update_permissions
from resources.models import ContentObject

from . import mixins
from .. import serializers


def patch_actions(self, request):
Expand Down Expand Up @@ -32,19 +38,27 @@ class ProjectResources(APIPermissionRequiredMixin,
filters.SearchFilter,
filters.OrderingFilter,)
filter_fields = ('archived',)
search_fields = ('name', 'description', 'file',)
ordering_fields = ('name', 'description', 'file',)
search_fields = ('name', 'description', 'original_file')
ordering_fields = ('name', 'description', 'file',
'contributor__username', 'last_updated')
permission_required = {
'GET': 'resource.list',
'POST': update_permissions('resource.add')
}

serializer_class = serializers.ResourceSerializer
permission_filter_queryset = filter_archived_resources
use_resource_library_queryset = True

def get_queryset(self):
proj = self.get_content_object()
related_content_objs = ContentObject.objects.filter(
resource=OuterRef('id'),
content_type=ContentType.objects.get_for_model(proj),
object_id=proj.id)
return super().get_queryset().prefetch_related(
'content_objects__content_type')
'content_objects__content_type').annotate(
attached=Exists(related_content_objs))


class ProjectResourcesDetail(APIPermissionRequiredMixin,
Expand Down
3 changes: 0 additions & 3 deletions cadasta/templates/accounts/user_dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,3 @@ <h3 class="panel-title inline">
{% endblock %}

{% block modals %}{% endblock %}



85 changes: 45 additions & 40 deletions cadasta/templates/core/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -196,54 +196,59 @@
</div>
{% block modals %} {% endblock %}

<script type="text/javascript" src="{% static 'babel-polyfill/6.26.0/polyfill.min.js' %}"></script>
<script type="text/javascript" src="{% static 'datatables/1.10.8/js/datatables.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/dataTables.conditionalPaging.js' %}"></script>
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
<script src="{% static 'js/parsleyConfig.js' %}"></script>
<script src="{% static 'js/parsley.js' %}"></script>
<script type="text/javascript" src="{% static 'js/parsleyConfig.js' %}"></script>
<script type="text/javascript" src="{% static 'js/parsley.js' %}"></script>

{% block extra_script %}{% endblock %}
<script>
$(document).ready(function () {
$('.datatable').DataTable({
conditionalPaging: true,
"dom": '<"table-search clearfix"f>t<"table-pagination"p><"table-entries"i><"table-num"l>',
"language": {
"emptyTable": "{% trans "No data available in table" %}",
"info": "{% trans "Showing _START_ - _END_ of _TOTAL_" %}",
"infoEmpty": "{% trans "Showing 0 - 0 of 0" %}",
"infoFiltered": "{% trans "(filtered from _MAX_ total rows)" %}",
"lengthMenu": '<select class="form-control input-sm">'+
' <option value="10">{% trans "10 per page" %}</option>'+
' <option value="25">{% trans "25 per page" %}</option>'+
' <option value="50">{% trans "50 per page" %}</option>'+
' <option value="100">{% trans "100 per page" %}</option>'+
'</select>',
"search": '<div class="input-group"><span class="input-group-addon"><span class="glyphicon glyphicon-search"></span></span>',
"searchPlaceholder": '{% trans "Search" %}',
"zeroRecords": "{% trans "No matching records found" %}",
"paginate": {
"next": '<span class="glyphicon glyphicon-triangle-right"></span>',
"previous": '<span class="glyphicon glyphicon-triangle-left"></span>',
"first": "{% trans "First" %}",
"last": "{% trans "Last" %}"
},
"aria": {
"sortAscending": ": {% trans "activate to sort column ascending" %}",
"sortDescending": ": {% trans "activate to sort column descending" %}"
$(document).ready(function () {
$('.datatable').DataTable({
conditionalPaging: true,
"dom": '<"table-search clearfix"f>t<"table-pagination"p><"table-entries"i><"table-num"l>',
"language": {
"emptyTable": "{% trans "No data available in table" %}",
"info": "{% trans "Showing _START_ - _END_ of _TOTAL_" %}",
"infoEmpty": "{% trans "Showing 0 - 0 of 0" %}",
"infoFiltered": "{% trans "(filtered from _MAX_ total rows)" %}",
"lengthMenu": '<select class="form-control input-sm">'+
' <option value="10">{% trans "10 per page" %}</option>'+
' <option value="25">{% trans "25 per page" %}</option>'+
' <option value="50">{% trans "50 per page" %}</option>'+
' <option value="100">{% trans "100 per page" %}</option>'+
'</select>',
"search": '<div class="input-group"><span class="input-group-addon"><span class="glyphicon glyphicon-search"></span></span>',
"searchPlaceholder": '{% trans "Search" %}',
"zeroRecords": "{% trans "No matching records found" %}",
"paginate": {
"next": '<span class="glyphicon glyphicon-triangle-right"></span>',
"previous": '<span class="glyphicon glyphicon-triangle-left"></span>',
"first": "{% trans "First" %}",
"last": "{% trans "Last" %}"
},
"aria": {
"sortAscending": ": {% trans "activate to sort column ascending" %}",
"sortDescending": ": {% trans "activate to sort column descending" %}"
}
}
}
});
$('.dropdown-toggle').dropdown();
$('#language').change(function() { this.form.submit(); });
$('[data-parsley-required="true"]')
.closest('.form-group')
.children('label.control-label')
.addClass('required');
});
$('.dropdown-toggle').dropdown();
$('#language').change(function() { this.form.submit(); });
$('[data-parsley-required="true"]')
.closest('.form-group')
.children('label.control-label')
.addClass('required');
});
window.setTimeout(function() {
$(".alert-success").fadeTo(500, 0).slideUp(500, function(){ $(this).hide(); });
}, 5000);
window.setTimeout(function() {
$(".alert-success")
.fadeTo(500, 0)
.slideUp(500, function(){
$(this).hide();
});
}, 5000);
</script>
<script>
// User tracking
Expand Down
Loading

0 comments on commit fdb32e4

Please sign in to comment.