Commit 7dea6479 authored by Robin Tissot's avatar Robin Tissot
Browse files

Merge branch 'develop'

parents 95b35580 8ff9a243
......@@ -19,6 +19,7 @@ from core.models import (Document,
LineTranscription,
BlockType,
LineType,
Script,
OcrModel)
from core.tasks import (segtrain, train, segment, transcribe)
......@@ -51,6 +52,12 @@ class ImageField(serializers.ImageField):
return data
class ScriptSerializer(serializers.ModelSerializer):
class Meta:
model = Script
fields = '__all__'
class PartMoveSerializer(serializers.ModelSerializer):
index = serializers.IntegerField()
......@@ -99,6 +106,8 @@ class LineTypeSerializer(serializers.ModelSerializer):
class DocumentSerializer(serializers.ModelSerializer):
main_script = serializers.SlugRelatedField(slug_field='name',
queryset=Script.objects.all())
transcriptions = TranscriptionSerializer(many=True, read_only=True)
valid_block_types = BlockTypeSerializer(many=True, read_only=True)
valid_line_types = LineTypeSerializer(many=True, read_only=True)
......@@ -106,12 +115,18 @@ class DocumentSerializer(serializers.ModelSerializer):
class Meta:
model = Document
fields = ('pk', 'name', 'transcriptions',
fields = ('pk', 'name', 'transcriptions', 'main_script', 'read_direction',
'valid_block_types', 'valid_line_types', 'parts_count')
def get_parts_count(self, document):
return document.parts.count()
def validate_main_script(self, value):
try:
return Script.objects.get(name=value)
except Script.DoesNotExist:
raise serializers.ValidationError('This script does not exists in the database.')
class PartSerializer(serializers.ModelSerializer):
image = ImageField(required=False, thumbnails=['card', 'large'])
......@@ -133,7 +148,8 @@ class PartSerializer(serializers.ModelSerializer):
'workflow',
'order',
'recoverable',
'transcription_progress'
'transcription_progress',
'source'
)
def create(self, data):
......
......@@ -11,9 +11,11 @@ from api.views import (DocumentViewSet,
BlockTypeViewSet,
LineTypeViewSet,
LineTranscriptionViewSet,
ScriptViewSet,
OcrModelViewSet)
router = routers.DefaultRouter()
router.register(r'scripts', ScriptViewSet)
router.register(r'documents', DocumentViewSet)
router.register(r'user', UserViewSet)
router.register(r'types/block', BlockTypeViewSet)
......
......@@ -11,7 +11,7 @@ from django.urls import reverse
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework import status
from rest_framework.viewsets import ModelViewSet
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
from rest_framework.pagination import PageNumberPagination
from rest_framework.serializers import PrimaryKeyRelatedField
......@@ -31,6 +31,7 @@ from api.serializers import (UserOnboardingSerializer,
SegmentSerializer,
TrainSerializer,
SegTrainSerializer,
ScriptSerializer,
TranscribeSerializer,
OcrModelSerializer)
......@@ -43,6 +44,7 @@ from core.models import (Document,
Transcription,
LineTranscription,
OcrModel,
Script,
AlreadyProcessingException)
from core.tasks import recalculate_masks
......@@ -67,6 +69,12 @@ class UserViewSet(ModelViewSet):
return Response(status=status.HTTP_200_OK)
class ScriptViewSet(ReadOnlyModelViewSet):
queryset = Script.objects.all()
paginate_by = 20
serializer_class = ScriptSerializer
class DocumentViewSet(ModelViewSet):
queryset = Document.objects.all()
serializer_class = DocumentSerializer
......
......@@ -187,7 +187,8 @@ class Document(models.Model):
read_direction = models.CharField(
max_length=3,
choices=READ_DIRECTION_CHOICES,
default=READ_DIRECTION_LTR
default=READ_DIRECTION_LTR,
help_text=_("The read direction describes the order of the elements in the document, in opposition with the text direction which describes the order of the words in a line and is set by the script.")
)
typology = models.ForeignKey(DocumentType, null=True, blank=True, on_delete=models.SET_NULL)
......
from django.urls import path, include
from users.views import (SendInvitation, AcceptInvitation, AcceptGroupInvitation, ContactUsView,
ProfileInfos, ProfileGroupListCreate, ProfileApiKey, ProfileFiles,
ProfileInfos, ProfileGroupListCreate, ProfileApiKey,
ProfileFiles, ProfileInvitations,
GroupDetail, RemoveFromGroup, LeaveGroup, TransferGroupOwnership)
from django.contrib.auth.decorators import permission_required
......@@ -11,6 +12,7 @@ urlpatterns = [
path('profile/apikey/', ProfileApiKey.as_view(), name='profile-api-key'),
path('profile/files/', ProfileFiles.as_view(), name='profile-files'),
path('profile/teams/', ProfileGroupListCreate.as_view(), name='profile-team-list'),
path('profile/invitations/', ProfileInvitations.as_view(), name='profile-invites-list'),
path('teams/<int:pk>/', GroupDetail.as_view(), name='team-detail'),
path('teams/<int:pk>/remove/', RemoveFromGroup.as_view(), name='team-remove-user'),
path('teams/<int:pk>/leave/', LeaveGroup.as_view(), name='team-leave'),
......
......@@ -189,6 +189,19 @@ class ProfileFiles(LoginRequiredMixin, TemplateView):
return context
class ProfileInvitations(LoginRequiredMixin, TemplateView):
template_name = 'users/profile_invitations.html'
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
invites = self.request.user.invitations_sent.all()
paginator = Paginator(invites, 25)
context['is_paginated'] = paginator.count != 0
page_number = self.request.GET.get('page')
context['page_obj'] = paginator.get_page(page_number)
return context
class ProfileGroupListCreate(LoginRequiredMixin, SuccessMessageMixin, CreateView):
"""
Both were we create new groups and list them
......
{% extends 'core/document_nav.html' %}
{% load i18n staticfiles bootstrap %}
{% load i18n staticfiles bootstrap json %}
{% block head_title %}{% if object %}{% trans "Update a Document" %}{% else %}{% trans "Create a new Document" %}{% endif %}{% endblock %}
{% block nav-doc-active %}active{% endblock %}
......@@ -173,9 +173,11 @@
</script>
{{ block.super }}
<script type="text/javascript">
$(document).ready(function(){
bootDocumentForm();
bootHelp();
});
$(document).ready(function(){
var scripts = { {% for script in form.fields.main_script.queryset %}
'{{script.pk}}':'{{script.text_direction}}',{% endfor %} };
bootDocumentForm(scripts);
bootHelp();
});
</script>
{% endblock %}
{% load export_tags %}<?xml version="1.0" encoding="UTF-8"?>
<alto xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.loc.gov/standards/alto/ns-v4#"
xsi:schemaLocation="http://www.loc.gov/standards/alto/ns-v4# https://gitlab.inria.fr/scripta/escriptorium/-/raw/develop/app/escriptorium/static/alto-4-1-baselines.xsd">
xsi:schemaLocation="http://www.loc.gov/standards/alto/ns-v4# http://www.loc.gov/standards/alto/v4/alto-4-2.xsd">
<Description>
<MeasurementUnit>pixel</MeasurementUnit>
<sourceImageInformation>
<fileName>{{ part.filename }}</fileName>
{% if part.source %}<fileIdentifier>{{ part.source }}</fileIdentifier>{% endif %}
</sourceImageInformation>
</Description>
{% if valid_block_types or valid_line_types %}
......
{% load export_tags %}<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<PcGts xmlns="http://schema.primaresearch.org/PAGE/gts/pagecontent/2019-07-15" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://schema.primaresearch.org/PAGE/gts/pagecontent/2019-07-15 http://schema.primaresearch.org/PAGE/gts/pagecontent/2019-07-15/pagecontent.xsd">
<Metadata>
<Metadata{% if part.source %} externalRef="{{part.source}}"{% endif %}>
<Creator>escriptorium</Creator>
<Created>{% current_time %}</Created>
<LastChange>{% current_time %}</LastChange>
<LastChange>{% current_time %}</LastChange>
</Metadata>
<Page imageFilename="{{ part.filename }}" imageWidth="{{ part.image.width }}" imageHeight="{{ part.image.height }}">
......
......@@ -9,6 +9,7 @@
<a class="nav-link {% block key-tab-active %}{% endblock %}" id="nav-key-tab" href="{% url 'profile-api-key' %}" role="tab">{% trans "Api key" %}</a>
<a class="nav-link {% block files-tab-active %}{% endblock %}" id="nav-files-tab" href="{% url 'profile-files' %}" role="tab">{% trans "Files" %}</a>
<a class="nav-link {% block team-tab-active %}{% endblock %}" id="nav-infos-tab" href="{% url 'profile-team-list' %}" role="tab">{% trans "Teams" %}</a>
<a class="nav-link {% block invites-tab-active %}{% endblock %}" id="nav-infos-tab" href="{% url 'profile-invites-list' %}" role="tab">{% trans "Invitations" %}</a>
</div>
<div class="col-md-8 tab-content" id="v-pills-tabContent">
......
......@@ -3,8 +3,6 @@
{% block infos-tab-active %}{% endblock %}
{% block key-tab-active %}active{% endblock %}
{% block files-tab-active %}{% endblock %}
{% block team-tab-active %}{% endblock %}
{% block tab-content %}
{% trans "API Authentication Token:" %} {{ api_auth_token.key }}
......
......@@ -2,9 +2,7 @@
{% load i18n static %}
{% block infos-tab-active %}{% endblock %}
{% block key-tab-active %}{% endblock %}
{% block files-tab-active %}active{% endblock %}
{% block team-tab-active %}{% endblock %}
{% block tab-content %}
{% for fpath, fname in page_obj %}
......
......@@ -2,12 +2,9 @@
{% load i18n bootstrap static %}
{% block infos-tab-active %}{% endblock %}
{% block key-tab-active %}{% endblock %}
{% block files-tab-active %}{% endblock %}
{% block team-tab-active %}active{% endblock %}
{% block tab-content %}
<h4>{% trans "Create a new Team" %}</h4>
<form method="post">
{% csrf_token %}
......
{% extends "users/profile.html" %}
{% load i18n static %}
{% block infos-tab-active %}{% endblock %}
{% block invites-tab-active %}active{% endblock %}
{% block tab-content %}
<table class="table">
{% for invite in page_obj %}
<tr>
<td>{{invite.recipient_email|default:invite.recipient}}</td>
<td title="Into group">{{invite.group|default:""}}</td>
<td>{{invite.get_workflow_state_display}}</td>
</tr>
{% empty %}
{% trans "You didn't send any invitations yet." %}
{% endfor %}
</table>
{% include "includes/pagination.html" %}
{% endblock %}
......@@ -12,7 +12,7 @@ django-redis==4.10.0
psycopg2-binary==2.7.6
django-ordered-model==3.1.1
easy-thumbnails==2.5
git+https://github.com/mittagessen/kraken.git@3.0b23#egg=kraken
git+https://github.com/mittagessen/kraken.git@3.0b24#egg=kraken
django-cleanup==5.1.0
djangorestframework==3.9.2
drf-nested-routers==0.91
......
......@@ -3,8 +3,6 @@ version: "3.9"
services:
### to customize the homepage, uncomment this
#app:
# environment:
# - CUSTOM_HOME=True
# volumes:
# - $PWD/app/homepage
......
......@@ -18,7 +18,6 @@ services:
expose:
- 8000
channelserver:
<<: *app
command: daphne --bind 0.0.0.0 --port 5000 -v 1 escriptorium.asgi:application
......
'use strict';
/**** Metadata stuff ****/
export function bootDocumentForm() {
export function bootDocumentForm(scripts) {
// delete a metadata row
$('.js-metadata-delete').click(function(ev) {
var btn = ev.target;
......@@ -119,4 +119,19 @@ export function bootDocumentForm() {
window.location.hash = this.hash;
}
});
// When selecting a rtl script, select rtl read direction
// flash shortly the read direction to tell the user when it changed
$('#id_main_script').on('change', function(ev) {
let dir = scripts[ev.target.value];
if (dir=='horizontal-rl') {
if ($('#id_read_direction').val() !== 'rtl') {
$('#id_read_direction').val('rtl').addClass('is-valid').removeClass('is-valid', 1000);
}
} else {
if ($('#id_read_direction').val() !== 'ltr') {
$('#id_read_direction').val('ltr').addClass('is-valid').removeClass('is-valid', 1000);
}
}
});
}
......@@ -14,10 +14,10 @@ export const initialState = () => ({
// Manage panels visibility through booleans
// Those values are initially populated by localStorage
visible_panels: {
source: userProfile.get('source-panel'),
segmentation: userProfile.get('segmentation-panel'),
visualisation: userProfile.get('visualisation-panel'),
diplomatic: userProfile.get('diplomatic-panel')
source: userProfile.get('visible-panels')?userProfile.get('visible-panels').source:false,
segmentation: userProfile.get('visible-panels')?userProfile.get('visible-panels').segmentation:true,
visualisation: userProfile.get('visible-panels')?userProfile.get('visible-panels').visualisation:true,
diplomatic: userProfile.get('visible-panels')?userProfile.get('visible-panels').diplomatic:false
},
})
......@@ -70,7 +70,7 @@ export const actions = {
commit('setVisiblePanels', update)
// Persist final value in user profile
userProfile.set(panel + '-panel', state.visible_panels[panel])
userProfile.set('visible-panels', state.visible_panels)
}
}
......
......@@ -94,7 +94,7 @@
class="btn btn-sm btn-info fas fa-question help nav-item ml-2">
</button>
<div id="segmentation-help" class="alert alert-primary help-text collapse">
<button type="button" class="close" aria-label="Close">
<button type="button" data-toggle="collapse" data-target="#segmentation-help" class="close" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<help></help>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment