Commit 4db817a0 authored by Robin Tissot's avatar Robin Tissot
Browse files

wip merge

parent 72ab5c57
......@@ -19,8 +19,8 @@ router.register(r'documents', DocumentViewSet)
router.register(r'user', UserViewSet)
router.register(r'types/block', BlockTypeViewSet)
router.register(r'types/line', LineTypeViewSet)
router.register(r'documentstags', DocumentTagViewSet)
documents_router = routers.NestedSimpleRouter(router, r'documents', lookup='document')
documents_router.register(r'tags', DocumentTagViewSet)
documents_router.register(r'parts', PartViewSet, basename='part')
documents_router.register(r'transcriptions', DocumentTranscriptionViewSet, basename='transcription')
documents_router.register(r'models', OcrModelViewSet, basename='model')
......
......@@ -172,20 +172,20 @@ class DocumentViewSet(ModelViewSet):
@action(detail=True, methods=['post'])
def transcribe(self, request, pk=None):
return self.get_process_response(request, TranscribeSerializer)
@action(detail=False, methods=['post', 'get'])
def get_document_by_tags(self, request):
documents = self.get_custom_queryset(self.request.data.get('chain'))
paginate_documents = Paginator(documents, 10)
tags_documents = list(DocumentTag.objects.filter(document__in=documents).distinct().values('pk', 'name', 'priority'))
return Response(status=status.HTTP_200_OK, data={'documents': json.dumps(DocumentListSerializer(paginate_documents.get_page(1), many=True).data), 'dtags': tags_documents})
def get_custom_queryset(self, chain=None):
if (len(chain.strip()) != 0) and (chain != '0'):
documents = Document.objects.for_user(self.request.user).select_related('owner', 'main_script').prefetch_related('parts').annotate(parts_updated_at=Max('parts__updated_at')).annotate(owner_document=F('owner__username')).filter(tags__pk__in=list(map(int, chain.split(','))))
else:
documents = Document.objects.for_user(self.request.user).select_related('owner', 'main_script').prefetch_related('parts').annotate(parts_updated_at=Max('parts__updated_at')).annotate(owner_document=F('owner__username'))
return documents
# @action(detail=False, methods=['post', 'get'])
# def get_document_by_tags(self, request):
# documents = self.get_custom_queryset(self.request.data.get('chain'))
# paginate_documents = Paginator(documents, 10)
# tags_documents = list(DocumentTag.objects.filter(document__in=documents).distinct().values('pk', 'name', 'priority'))
# return Response(status=status.HTTP_200_OK, data={'documents': json.dumps(DocumentListSerializer(paginate_documents.get_page(1), many=True).data), 'dtags': tags_documents})
# def get_custom_queryset(self, chain=None):
# if (len(chain.strip()) != 0) and (chain != '0'):
# documents = Document.objects.for_user(self.request.user).select_related('owner', 'main_script').prefetch_related('parts').annotate(parts_updated_at=Max('parts__updated_at')).annotate(owner_document=F('owner__username')).filter(tags__pk__in=list(map(int, chain.split(','))))
# else:
# documents = Document.objects.for_user(self.request.user).select_related('owner', 'main_script').prefetch_related('parts').annotate(parts_updated_at=Max('parts__updated_at')).annotate(owner_document=F('owner__username'))
# return documents
class DocumentPermissionMixin():
def get_queryset(self):
......@@ -476,55 +476,51 @@ class OcrModelViewSet(DocumentPermissionMixin, ModelViewSet):
return Response({'status': 'failed'}, status=400)
return Response({'status': 'canceled'})
class DocumentTagViewSet(ModelViewSet):
queryset = DocumentTag.objects.all()
serializer_class = TagDocumentSerializer
lookup_field = 'pk'
@action(detail=True, methods=['GET'])
def remove(self, request, id=None):
self.get_object().delete()
return JsonResponse({'status': status.HTTP_200_OK})
@action(detail=False, methods=['get', 'post'])
def get_tags(self, request):
tags = Document.objects.get(pk=self.request.data.get('document')).tags
listtags = []
return JsonResponse({'result': json.dumps(TagDocumentSerializer(tags, many=True).data), 'status': status.HTTP_200_OK})
@action(detail=False, methods=['get', 'post'])
def get_custom_tags(self, request):
return JsonResponse({'tag': list(DocumentTag.objects.all().values('pk', 'name')), 'tagi': list(PartTag.objects.all().values('pk', 'name')), 'status': status.HTTP_200_OK})
@action(detail=False, methods=['get', 'post'])
def get_unlinked_tags(self, request):
tags = DocumentTag.objects.all()
dtags = get_object_or_404(Document, pk=self.request.data.get('document')).tags
tagexcudes = tags.exclude(pk__in=list(dtags.values_list('pk', flat=True)))
listtags = []
return JsonResponse({'tags': json.dumps(TagDocumentSerializer(tagexcudes, many=True).data), 'status': status.HTTP_200_OK})
@action(detail=False, methods=['get', 'post'])
def unassign(self, request):
dict_data = json.loads(list(self.request.data)[0])
document = Document.objects.get(pk=int(dict_data['document']))
dtags = DocumentTag.objects.filter(pk__in=list(map(int, dict_data['selectedtags'].split(','))))
with transaction.atomic():
for tag in dtags:
document.tags.remove(tag)
return JsonResponse({'status': status.HTTP_200_OK})
@action(detail=False, methods=['get', 'post'])
def assign(self, request):
dict_data = json.loads(list(self.request.data)[0])
tag_pk = dict_data['pk']
document = get_object_or_404(Document, pk=int(dict_data['document']))
if len(tag_pk.strip()) != 0:
document.tags.add(get_object_or_404(DocumentTag, pk=int(tag_pk)))
return JsonResponse({'action': 'assign', 'status': status.HTTP_200_OK})
else:
tag = DocumentTag.objects.create(name=dict_data['name'], priority=dict_data['priority'])
document.tags.add(tag)
return JsonResponse({'result': json.dumps(TagDocumentSerializer(tag).data), 'action': 'add', 'status': status.HTTP_200_OK})
def get_queryset(self):
qs = super().get_queryset()
return qs.filter(document=self.kwargs.get('document_pk'))
def perform_create(self, serializer):
obj = serializer.save()
document = Document.objects.get(pk=self.kwargs.get('document_pk'))
document.tags.add(obj)
def perform_destroy(self, instance):
document = Document.objects.get(pk=self.kwargs.get('document_pk'))
document.tags.remove(instance)
# @action(detail=False, methods=['get', 'post'])
# def get_unlinked_tags(self, request):
# tags = DocumentTag.objects.all()
# dtags = get_object_or_404(Document, pk=self.request.data.get('document')).tags
# tagexcudes = tags.exclude(pk__in=list(dtags.values_list('pk', flat=True)))
# listtags = []
# return JsonResponse({'tags': json.dumps(TagDocumentSerializer(tagexcudes, many=True).data), 'status': status.HTTP_200_OK})
# @action(detail=False, methods=['get', 'post'])
# def unassign(self, request):
# dict_data = json.loads(list(self.request.data)[0])
# document = Document.objects.get(pk=int(dict_data['document']))
# dtags = DocumentTag.objects.filter(pk__in=list(map(int, dict_data['selectedtags'].split(','))))
# with transaction.atomic():
# for tag in dtags:
# document.tags.remove(tag)
# return JsonResponse({'status': status.HTTP_200_OK})
# @action(detail=False, methods=['get', 'post'])
# def assign(self, request):
# dict_data = json.loads(list(self.request.data)[0])
# tag_pk = dict_data['pk']
# document = get_object_or_404(Document, pk=int(dict_data['document']))
# if len(tag_pk.strip()) != 0:
# document.tags.add(get_object_or_404(DocumentTag, pk=int(tag_pk)))
# return JsonResponse({'action': 'assign', 'status': status.HTTP_200_OK})
# else:
# tag = DocumentTag.objects.create(name=dict_data['name'], priority=dict_data['priority'])
# document.tags.add(tag)
# return JsonResponse({'result': json.dumps(TagDocumentSerializer(tag).data), 'action': 'add', 'status': status.HTTP_200_OK})
......@@ -133,6 +133,7 @@ class DocumentMetadata(models.Model):
def __str__(self):
return '%s:%s' % (self.document.name, self.key.name)
class Tag(models.Model):
name = models.CharField(max_length=100, blank=False)
PRIORITY_VERY_HIGH = 5
......@@ -142,26 +143,28 @@ class Tag(models.Model):
PRIORITY_VERY_LOW = 1
priorityitems = (
(PRIORITY_VERY_HIGH, "Very high"),
(PRIORITY_HIGH, "High"),
(PRIORITY_MEDIUM, 'Medium'),
(PRIORITY_HIGH, "High"),
(PRIORITY_MEDIUM, 'Medium'),
(PRIORITY_LOW, 'Low'),
(PRIORITY_VERY_LOW, 'Very low'),
)
)
priority = models.PositiveSmallIntegerField(choices=priorityitems, default=PRIORITY_VERY_LOW)
class Meta:
abstract = True
def __str__(self):
return self.name
class DocumentTag(Tag):
pass
class PartTag(Tag):
pass
class DocumentManager(models.Manager):
def get_queryset(self):
return super().get_queryset().select_related('typology')
......
......@@ -12,21 +12,21 @@ class DocumentTestCase(TestCase):
factory = CoreFactory()
doc = factory.make_document()
factory.make_document(owner=doc.owner)
self.user = doc.owner
group = Group.objects.create(name='test group')
self.user.groups.add(group)
doc = factory.make_document() # another owner
doc.shared_with_users.add(doc.owner)
doc = factory.make_document()
doc.shared_with_groups.add(group)
def test_list(self):
self.client.force_login(self.user)
uri = reverse('documents-list')
with self.assertNumQueries(9):
with self.assertNumQueries(13):
# Note: 1 query / document to fetch the first picture
# can be improved
resp = self.client.get(uri)
......@@ -63,4 +63,3 @@ class DocumentTestCase(TestCase):
})
self.assertEqual(resp.status_code, 302)
self.assertEqual(Document.objects.count(), 5) # 4 created in setup
......@@ -34,6 +34,7 @@ class Home(TemplateView):
context['KRAKEN_VERSION'] = settings.KRAKEN_VERSION
return context
class DocumentsList(LoginRequiredMixin, ListView):
model = Document
paginate_by = 10
......@@ -41,23 +42,25 @@ class DocumentsList(LoginRequiredMixin, ListView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['tags_documents'] = list(DocumentTag.objects.filter(document__in=self.get_queryset()).distinct().values('pk', 'name', 'priority'))
context['tags_parts'] = list(PartTag.objects.all().values('pk', 'name', 'priority'))
context['document_list'] = json.dumps(DocumentListSerializer(self.get_paginate_queryset(), many=True).data)
tags_filtrs = self.request.GET.getlist('tagd')
context['filters'] = ','.join(tags_filtrs) if (tags_filtrs is not None) else '0'
# context['tags_parts'] = list(PartTag.objects.all().values('pk', 'name', 'priority'))
return context
def get_queryset(self):
qs = Document.objects.for_user(self.request.user).select_related('owner', 'main_script').prefetch_related('parts').annotate(parts_updated_at=Max('parts__updated_at')).annotate(owner_document=F('owner__username'))
qs = (Document.objects
.for_user(self.request.user)
.select_related('owner', 'main_script')
.prefetch_related('parts')
.annotate(parts_updated_at=Max('parts__updated_at')))
tags_filters = self.request.GET.getlist('tagd')
if tags_filters:
qs = qs.filter(tags__pk__in=tags_filters)
qs = qs.filter(tags__name__in=tags_filters)
return qs
def get_paginate_queryset(self):
paginator = Paginator(self.get_queryset(), self.paginate_by)
page_number = 1 if (self.request.GET.get('page') is None) else self.request.GET.get('page')
return paginator.get_page(page_number)
# def get_paginate_queryset(self):
# paginator = Paginator(self.get_queryset(), self.paginate_by)
# page_number = 1 if (self.request.GET.get('page') is None) else self.request.GET.get('page')
# return paginator.get_page(page_number)
class DocumentMixin():
def get_success_url(self):
......
{% extends 'base.html' %}
{% load i18n staticfiles thumbnail %}
{% load i18n staticfiles thumbnail json %}
{% block head_title %}{% trans "My Documents" %}{% endblock %}
{% block styles %}
{{ block.super }}
<link rel="stylesheet" href="https://unpkg.com/multiple-select@1.5.2/dist/multiple-select.min.css">
{% endblock %}
{% block nav-doc-list-class %}active{% endblock %}
{% block body %}
<div class="row">
<div class="col-lg-12">
<a href="{% url 'document-create' %}" class="btn btn-success float-sm-right">{% trans 'Create new' %}</a>
<h2>{% trans "My Documents" %}</h2>
</div>
<div class="col-lg-12">
<a href="{% url 'document-create' %}" class="btn btn-success float-sm-right">{% trans 'Create new' %}</a>
<h2>{% trans "My Documents" %}</h2>
</div>
</div>
<div id="appdoc">
<documentmanage :chainflt="'{{filters}}'"
:tags="{{tags_documents}}"
:tagsimg="{{tags_parts}}"
:documents="{{document_list}}"
>
</documentmanage>
<div id="document_list" class="row">
<div class="col-2">
<tagsselector doc-tags="{{ tags_documents|jsond }}"></tagsselector>
</div>
<div class="col">
<table class="table table-hover">
<tbody>
{% for document in document_list %}
<tr>
<td>
{% with part=document.parts.first %}
{% if part %}
<img src="{{ part.image|thumbnail_url:'list' }}" />
{% endif %}
{% endwith %}
</td>
<th>
{{ document.name }}
{% if document.typology %}<br><span class="text-muted"><small>{{ document.typology }}</small></span>{% endif %}
</th>
<td>
<span title="Owner">{{ document.owner }}</span>
<br>
{% with groups=document.shared_with_groups.all %}{% if groups %}</br><span title="{% trans 'Shared with' %}" class="text-muted"><small>{{ groups|join:', ' }}</small></span>{% endif %}
{% endwith %}</td>
</td>
<td title="Last modified on">
<span class="text-muted">
<small>{{ document.parts_updated_at|date }}</small>
</span>
</td>
<td>
{% blocktrans with count=document.parts.count %}{{ count }} image(s).{% endblocktrans %}
</td>
<td>
{% for tag in document.tags.all %}
<span class="badge badge-{{tag.priority}}">{{tag.name|lower}}</span>
{% endfor %}
<a href="#"
title="Manage tags"
class="btn btn-info btn-sm set-tag-meta btn-primary"
data-toggle="modal"
data-target="#tagAssignModal">
<i class="fas fa fa-tags"></i></a>
</td>
<td class="text-right">
<a href="{% url 'document-update' pk=document.pk %}" class="btn btn-info" title="{% trans 'Edit' %}"><i class="fas fa-edit"></i></a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<tagassignmodal></tagassignmodal>
</div>
</div>
{% include 'includes/pagination.html' %}
{% endblock %}
......@@ -36,4 +82,4 @@
<script src="{% static 'doclist.js' %}" type="text/javascript"></script>
{% endblock %}
\ No newline at end of file
{% endblock %}
import Vue from 'vue'
import store from '../editor/index';
import DocumentList from '../../vue/components/DocumentList.vue';
export var partVM = new Vue({
el: "#appdoc",
// import DocumentList from '../../vue/components/DocumentList.vue';
import TagsSelector from '../../vue/components/TagsSelector.vue';
import ModalAddTags from '../../vue/components/ModalAddTags.vue';
import ModalRemoveTags from '../../vue/components/ModalRemoveTags.vue';
export var doclistVM = new Vue({
el: "#document_list",
store,
components: {
'documentmanage': DocumentList,
'tagsselector': TagsSelector,
'tagassignmodal': ModalAddTags,
'tagunassignmodal': ModalRemoveTags,
}
});
\ No newline at end of file
});
<template>
<div class="col-12 col-sm-8 col-md-9 col-lg-10 col-md-offset-4">
<table class="table table-hover">
<tbody>
<tr v-for="doc in object_list" :key="doc.pk">
<td style="width: 120px;">
<img v-if="doc.parts.length > 0" v-bind:src="doc.parts[0].image.uri" style="width: 50%;">
</td>
<th>
{{ doc.name }}
<br><span v-if="doc.typology" class="text-muted"><small>{{ doc.typology }}</small></span>
</th>
<td>
<span title="Owner">{{titletext(doc.owner_document)}}</span>
<br>
<span title="Shared with" class="text-muted"><small>{{joinarray(doc.shared_with_groups)}}</small></span>
</td>
<td title="Last modified on"><span class="text-muted"><small>{{ formatdate(doc.parts_updated_at) }}</small></span></td>
<td v-if="doc.parts.length > 1">
{{ doc.parts.length }} image(s).
</td>
<td v-else>
{{ doc.parts.length }} image.
</td>
<td v-html="formattag(doc.tags)">
</td>
<td class="text-right">
<a v-bind:href="'/document/' + doc.pk +'/edit/'" class="btn btn-info" title="Edit"><i class="fas fa-edit"></i></a>
<a href="#" v-bind:name="doc.pk" title="Assign tag" class="btn btn-info set-tag-meta btn-primary" data-toggle="modal" data-target="#tagAssignModal" v-on:click="launchmodal(doc.pk)"><i class="fas fa fa-tags"></i></a>
<a href="#" v-bind:name="doc.pk" title="Remove tag" class="btn btn-info remove-tag-meta btn-danger" v-on:click="launchmodalunassign(doc.pk)"><i class="fas fa fa-tag"></i></a>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
......@@ -97,17 +63,17 @@ export default {
handler: function(nv) {
this.object_list = nv
},
immediate: true
immediate: true
}
}
}
</script>
<style scoped>
.table-hover td
.table-hover td
{
text-align: center;
text-align: center;
vertical-align: middle;
}
</style>
\ No newline at end of file
</style>
......@@ -17,14 +17,14 @@
<div v-bind:class="'checkbox'">
<input v-if="parseliststr(tagd.pk)" type="checkbox" v-bind:class="'filterdoc'" v-bind:id="tagd.pk" v-on:click="getFilterDocument" v-bind:name="'categorytag-D'" checked>
<input v-else type="checkbox" v-bind:class="'filterdoc'" v-bind:id="tagd.pk" v-on:click="getFilterDocument" v-bind:name="'categorytag-D'">
<label><span v-bind:class="'badge badge-pill badge-' + definepriority(tagd.priority)">{{tagd.name}}</span></label>
</div>
</li>
</ul>
</div>
</div>
<div v-bind:class="'item item-accordion'" v-bind:id="'item2'">
<h2>Document-part tags</h2>
<i v-bind:class="'fas fa-chevron-down'" v-bind:id="'p'" v-on:click="animatefunction('p')"></i>
......@@ -46,7 +46,7 @@
<div v-bind:class="'col-6'"><h6 v-bind:id="'selectall'" v-on:click="toggleselect('true')">Select all </h6></div>
<div v-bind:class="'col-6'"><h6 v-bind:id="'deselectall'" v-on:click="toggleselect('false')">Deselect all </h6></div>
</div>
</div>
</div>
</div>
......@@ -55,13 +55,11 @@
</div>
<modaltagadd></modaltagadd>
<modaltagremove></modaltagremove>
</div>
</div>
</template>
<script>
import DocList from './DocList.vue';
import ModalAddTags from './ModalAddTags.vue';
import ModalRemoveTags from './ModalRemoveTags.vue';
// import DocList from './DocList.vue';
export default {
props: [
......@@ -70,11 +68,11 @@ export default {
'chainflt',
'documents'
],
data(){
return {
doctags: []
}
},
// data(){
// return {
// doctags: []
// }
// },
created(){
this.$store.commit('documentslist/setChainFilter', this.chainflt);
this.$store.commit('documentslist/setDocTags', this.tags);
......@@ -141,12 +139,10 @@ export default {
$.each($("input.filterdoc:checkbox"), function(){
$(this).prop('checked', bool)
});
this.getFilterDocument();
},
parseliststr(idd){
return (this.chainflt.toString().split(',').includes(idd.toString())) ? true : false;
},
definepriority(p){
let priority;
switch (parseInt(p)) {
......@@ -173,7 +169,7 @@ export default {
handler: function(nv) {
this.doctags = nv
},
immediate: true
immediate: true
}
}
......@@ -182,5 +178,4 @@ export default {
</script>
<style scoped>
</style>
\ No newline at end of file
</style>
<template>
<div v-if="tags" class="col md-4">
<span>Document Tags</span>
<ul class="p-0 ckeckbox-list">
<li v-for="tagd in tags" :key="tagd.pk">
<input type="checkbox"
class=""
v-bind:id="tagd.pk"
v-bind:value="tagd.name"
v-model="checkedTags">
<label>
<span v-bind:class="'badge badge-pill badge-' + definepriority(tagd.priority)">{{tagd.name}}</span>
</label>
</li>
</ul>
<!-- <div class="item item-accordion"> -->
<!-- <h2>Document-part tags</h2> -->
<!-- <i class="fas fa-chevron-down"></i> -->
<!-- <div class="info"> -->
<!-- <ul class="clearfix ckeckbox-list"> -->
<!-- <li v-for="tagi in parttagscid" :key="tagi.pk"> -->
<!-- <div class="checkbox"> -->
<!-- <input type="checkbox" -->
<!-- class="filterdoc" -->
<!-- v-bind:id="tagi.pk" -->
<!-- v-bind:name="'categorytag-P'"> -->
<!-- <label> -->
<!-- <span v-bind:class="'badge badge-pill badge-' + definepriority(tagi.priority)">{{tagi.name}}</span> -->
<!-- </label> -->
<!-- </div> -->
<!-- </li> -->
<!-- </ul> -->
<!-- </div> -->
<!-- </div> -->
</div>
</template>
<script>
export default {
props: [
'docTags',
],
created: function() {
this.tags = JSON.parse(this.docTags);
},
computed: {
checkedTags: {
get: function() {
let params = new URLSearchParams(location.search);
return params.getAll('tagd');
},
set: function(val) {
let params = new URLSearchParams(location.search);
params.append('tagd', val);
if (val.length) {
document.location.href = document.location.pathname+'?'+params;
} else {
document.location.href = document.location.