Skip to content
Snippets Groups Projects
Commit 0a1e10f4 authored by dcz's avatar dcz
Browse files

Merge branch 'master' into unified-frame-edit-view

# Conflicts:
#	unifier/models.py
parents 4885e545 58676fc0
Branches
Tags
No related merge requests found
Showing with 236 additions and 16 deletions
function update_entries() { function update_entries() {
const can_see_assignees = has_permission("auth.view_user"); const can_see_assignees = has_permission("users.view_assignment");
function is_assigned_to_user_renderer (data) { function is_assigned_to_user_renderer (data) {
return ( return (
...@@ -14,6 +14,7 @@ function update_entries() { ...@@ -14,6 +14,7 @@ function update_entries() {
columns: [ columns: [
{ data: 'lemma' }, { data: 'lemma' },
{ data: 'POS' }, { data: 'POS' },
{ render: data => (data && data.lexical_units && data.lexical_units.some(lu => lu.assignee_username !== null)) ? gettext("nie") : gettext("tak") },
can_see_assignees ? { data: 'assignee_username' } : { render: is_assigned_to_user_renderer }, can_see_assignees ? { data: 'assignee_username' } : { render: is_assigned_to_user_renderer },
] ]
}); });
...@@ -51,7 +52,7 @@ function setup_lexical_units_table(drilldown, lexical_units, can_see_assignees) ...@@ -51,7 +52,7 @@ function setup_lexical_units_table(drilldown, lexical_units, can_see_assignees)
</tr> </tr>
`).click(function () { `).click(function () {
$(this).addClass('table-primary').siblings().removeClass("table-primary"); $(this).addClass('table-primary').siblings().removeClass("table-primary");
get_entry($(drilldown.data("row")).data("entry"), false); // TODO replace with loading LU details view get_lexical_unit(lexical_unit.pk, $(drilldown.data("row")).data("entry"));
}); });
} }
var table = $(` var table = $(`
...@@ -75,3 +76,42 @@ function setup_lexical_units_table(drilldown, lexical_units, can_see_assignees) ...@@ -75,3 +76,42 @@ function setup_lexical_units_table(drilldown, lexical_units, can_see_assignees)
$("tbody", table).append(get_lexical_unit_row(lexical_unit)); $("tbody", table).append(get_lexical_unit_row(lexical_unit));
}); });
} }
function setup_notes($container, $template, lexical_unit_pk, entry) {
$container.html($template.children().clone());
$('.show-note-form', $container).click(function () {
$('.note-form', $container).html($('#note-form-template > div', $container).clone());
$('.hide-note-form', $container).click(function () { $('.note-form', $container).html(''); });
$('.add-note', $container).click(function () {
console.log($('textarea[name=note]', $container).val());
$.ajax({
type : 'post',
url : $('#note-form-template').data('url').replace('MODEL', 'meanings.LexicalUnit').replace('PK', lexical_unit_pk),
dataType : 'json',
data : { note: $('.note-form textarea[name=note]').val() },
timeout : 5000,
success : function (response) {
get_lexical_unit(lexical_unit_pk, entry);
},
error : function () {
alert(gettext('Nie udało się dodać notatki.'));
}
});
});
return false
});
$.ajax({
type: 'get',
url: $('.notes-table').data('url').replace('MODEL', 'meanings.LexicalUnit').replace('PK', lexical_unit_pk),
success: function (data) {
data.notes.map(function (note) {
$('.notes-table tbody', $container).append(`<tr><td>${note.note}</td><td>${note.owner_label}</td></tr>`);
});
}
})
}
function get_lexical_unit(pk, entry) {
get_entry(entry, false); // TODO replace with loading LU details view
setup_notes($('#lexical-unit-notes'), $('#lexical-unit-notes-template'), pk, entry);
}
function update_entries() { function update_entries() {
const can_see_assignees = has_permission("auth.view_user"); const can_see_assignees = has_permission("users.view_assignment");
function is_assigned_to_user_renderer (data) { function is_assigned_to_user_renderer (data) {
return ( return (
......
...@@ -12,4 +12,4 @@ ...@@ -12,4 +12,4 @@
{% block left_pane %}{% include "entries_list.html" %}{% endblock %} {% block left_pane %}{% include "entries_list.html" %}{% endblock %}
{% block right_pane %}{% include "entry_display.html" with show_tabs=True %}{% endblock %} {% block right_pane %}{% include "entry_display.html" %}{% endblock %}
{% load i18n %} {% load i18n %}
{% if show_tabs %}
<ul class="nav nav-pills nav-justified p-1" id="entryTabs" role="tablist"> <ul class="nav nav-pills nav-justified p-1" id="entryTabs" role="tablist">
<li class="nav-item mr-1"> <li class="nav-item mr-1">
<a class="btn btn-sm btn-outline-dark nav-link active" id="semantics-tab" data-toggle="tab" href="#semantics" role="tab" aria-controls="semantics" aria-selected="true"> <a class="btn btn-sm btn-outline-dark nav-link active" id="semantics-tab" data-toggle="tab" href="#semantics" role="tab" aria-controls="semantics" aria-selected="true">
...@@ -18,7 +17,6 @@ ...@@ -18,7 +17,6 @@
</a> </a>
</li> </li>
</ul> </ul>
{% endif %}
<div class="tab-content h-100 w-100 p-0" id="entryTabsContent"> <div class="tab-content h-100 w-100 p-0" id="entryTabsContent">
<div class="col h-100 w-100 p-0 tab-pane show active" id="semantics" role="tabpanel" aria-labelledby="semantics-tab"> <div class="col h-100 w-100 p-0 tab-pane show active" id="semantics" role="tabpanel" aria-labelledby="semantics-tab">
......
...@@ -12,4 +12,4 @@ ...@@ -12,4 +12,4 @@
{% block left_pane %}{% include "unification_entries_list.html" %}{% endblock %} {% block left_pane %}{% include "unification_entries_list.html" %}{% endblock %}
{% block right_pane %}{% include "entry_display.html" %}{% endblock %} {% block right_pane %}{% include "unification_lexical_unit_display.html" %}{% endblock %}
...@@ -5,7 +5,8 @@ ...@@ -5,7 +5,8 @@
<tr> <tr>
<th class="p-1">{% trans "Lemat" %}</th> <th class="p-1">{% trans "Lemat" %}</th>
<th class="p-1">{% trans "Część mowy" %}</th> <th class="p-1">{% trans "Część mowy" %}</th>
{% if perms.users.view_user %} <th class="p-1">{% trans "Do pobrania" %}</th>
{% if perms.users.view_assignment %}
<th class="p-1">{% trans "Semantyk" %}</th> <th class="p-1">{% trans "Semantyk" %}</th>
{% else %} {% else %}
<th class="p-1">{% trans "Moje (w opracowaniu)" %}</th> <th class="p-1">{% trans "Moje (w opracowaniu)" %}</th>
......
{% load i18n %}
<div class="tab-content h-100 w-100 p-0" id="entryTabsContent">
<div class="col h-100 w-100 p-0 tab-pane show active" id="semantics" role="tabpanel" aria-labelledby="semantics-tab">
<div class="row m-0 p-0" id="semantics-top-pane">
<div class="col h-100 px-1 pt-0 pb-0 overflow-auto" id="semantics-frames-pane">
<div id="semantics-frames"></div>
<div id="lexical-unit-notes-template" class="d-none">{% include 'notes.html' %}</div>
<div id="lexical-unit-notes"></div>
</div>
<div class="col h-100 px-1 pt-0 pb-0 overflow-auto" id="semantics-schemata-pane">
<div id="semantics-schemata"></div>
</div>
</div>
<div class="row m-0 p-0 overflow-auto" id="semantics-examples-pane">
<table id="semantics-examples" class="table table-sm table-hover">
<thead>
<tr>
<th scope="col">{% trans "Przykład" %}<i id="examples-argument"></i><i id="examples-lu"></i><i id="examples-schema"></i></th>
<th scope="col">{% trans "Źródło" %}</th>
<th scope="col">{% trans "Opinia" %}</th>
</tr>
</thead>
<tbody id="semantics-examples-list">
</tbody>
</table>
<p class="mx-1 my-1"id="semantics-no-examples">{% trans "Brak przykładów" %}</p>
</div>
</div>
<div class="col h-100 w-100 p-0 tab-pane" id="syntax" role="tabpanel" aria-labelledby="syntax-tab">
<div class="col w-100 px-1 pt-0 pb-0 overflow-auto" id="syntax-schemata-pane">
<div id="syntax-schemata"></div>
</div>
<div class="col w-100 p-0 overflow-auto" id="syntax-examples-pane">
<table id="syntax-examples" class="table table-sm table-hover">
<thead>
<tr>
<th scope="col">{% trans "Przykład" %}</th>
<th scope="col">{% trans "Źródło" %}</th>
<th scope="col">{% trans "Opinia" %}</th>
</tr>
</thead>
<tbody id="syntax-examples-list">
</tbody>
</table>
<p class="mx-1 my-1"id="syntax-no-examples">{% trans "Brak przykładów" %}</p>
</div>
</div>
<div class="col h-100 w-100 p-0 tab-pane" id="examples" role="tabpanel" aria-labelledby="examples-tab">
<table id="unmatched-examples" class="table table-sm table-hover">
<thead>
<tr>
<th scope="col">{% trans "Przykład" %}</th>
<th scope="col">{% trans "Źródło" %}</th>
<th scope="col">{% trans "Opinia" %}</th>
</tr>
</thead>
<tbody id="unmatched-examples-list">
</tbody>
</table>
<p class="mx-1 my-1"id="unmatched-no-examples">{% trans "Brak przykładów" %}</p>
</div>
</div>
...@@ -412,6 +412,7 @@ def get_entries(request): ...@@ -412,6 +412,7 @@ def get_entries(request):
{ {
'lexical_units': [ 'lexical_units': [
{ {
'pk': lu.pk,
'display': str(lu), 'display': str(lu),
'assignee_username': ( 'assignee_username': (
assignment.user.username if (assignment := lu.assignments.first()) else None assignment.user.username if (assignment := lu.assignments.first()) else None
......
...@@ -174,3 +174,5 @@ EMAIL_HOST_USER = get_environment('EMAIL_HOST_USER') ...@@ -174,3 +174,5 @@ EMAIL_HOST_USER = get_environment('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = get_environment('EMAIL_HOST_PASSWORD') EMAIL_HOST_PASSWORD = get_environment('EMAIL_HOST_PASSWORD')
EMAIL_USE_TLS = get_environment('EMAIL_USE_TLS', mapper=boolean_mapper) EMAIL_USE_TLS = get_environment('EMAIL_USE_TLS', mapper=boolean_mapper)
EMAIL_USE_SSL = get_environment('EMAIL_USE_SSL', mapper=boolean_mapper) EMAIL_USE_SSL = get_environment('EMAIL_USE_SSL', mapper=boolean_mapper)
SUPER_LEXICOGRAPHS_GROUP_NAME = 'Super Leksykografowie'
...@@ -7,6 +7,8 @@ from django.utils.translation import gettext_lazy as _ ...@@ -7,6 +7,8 @@ from django.utils.translation import gettext_lazy as _
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML, Layout, Fieldset, ButtonHolder, Submit from crispy_forms.layout import HTML, Layout, Fieldset, ButtonHolder, Submit
from users.models import Note
class UserForm(forms.ModelForm): class UserForm(forms.ModelForm):
group = forms.ModelChoiceField(queryset=Group.objects.all(), label=_('Grupa')) group = forms.ModelChoiceField(queryset=Group.objects.all(), label=_('Grupa'))
...@@ -102,3 +104,9 @@ password_reset_set_password_form_helper.layout = Layout( ...@@ -102,3 +104,9 @@ password_reset_set_password_form_helper.layout = Layout(
HTML(format_lazy('<a class="btn btn-sm btn-light" href="{}">{}</a>', reverse_lazy('dash'), _("Wróć"))), HTML(format_lazy('<a class="btn btn-sm btn-light" href="{}">{}</a>', reverse_lazy('dash'), _("Wróć"))),
) )
) )
class NoteForm(forms.ModelForm):
class Meta:
model = Note
fields = ["note"]
from django.conf import settings
from django.contrib.auth.models import Group, Permission, User from django.contrib.auth.models import Group, Permission, User
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from users.models import Assignment, Note
class Command(BaseCommand): class Command(BaseCommand):
def handle(self, **options): def handle(self, **options):
Permission.objects.update_or_create(
content_type=ContentType.objects.get_for_model(Note),
codename="view_all_notes",
defaults={"name": "View all notes"}
)
admins, __ = Group.objects.get_or_create(name='Admini') admins, __ = Group.objects.get_or_create(name='Admini')
admins.permissions.add( admins.permissions.add(
Permission.objects.get(codename='view_user', content_type=ContentType.objects.get_for_model(User)), self._get_permission(User, 'view_user'),
Permission.objects.get(codename='add_user', content_type=ContentType.objects.get_for_model(User)), self._get_permission(User, 'add_user'),
Permission.objects.get(codename='change_user', content_type=ContentType.objects.get_for_model(User)), self._get_permission(User, 'change_user'),
Permission.objects.get(codename='delete_user', content_type=ContentType.objects.get_for_model(User)), self._get_permission(User, 'delete_user'),
self._get_permission(Assignment, 'view_assignment'),
self._get_permission(Note, 'view_all_notes'),
) )
lexicographs, __ = Group.objects.get_or_create(name='Leksykografowie') lexicographs, __ = Group.objects.get_or_create(name='Leksykografowie')
lexicographs.permissions.add( lexicographs.permissions.add(
# TODO # TODO
) )
super_lexicographs, __ = Group.objects.get_or_create(name='Super Leksykografowie') super_lexicographs, __ = Group.objects.get_or_create(name=settings.SUPER_LEXICOGRAPHS_GROUP_NAME)
super_lexicographs.permissions.add( super_lexicographs.permissions.add(
# TODO self._get_permission(Assignment, 'view_assignment'),
self._get_permission(Note, 'view_all_notes'),
) )
def _get_permission(self, model, codename) -> Permission:
return Permission.objects.get(codename=codename, content_type=ContentType.objects.get_for_model(model))
from django.conf import settings
from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _
class Assignment(models.Model): class Assignment(models.Model):
...@@ -13,3 +15,31 @@ class Assignment(models.Model): ...@@ -13,3 +15,31 @@ class Assignment(models.Model):
unique_together = [ unique_together = [
("subject_ct", "subject_id"), ("subject_ct", "subject_id"),
] ]
class NoteQuerySet(models.QuerySet):
def for_user(self, user):
notes = (
self.filter(author=user).annotate(owner_label=models.Value(_('własne'), output_field=models.CharField()))
)
if user.has_perm('user.view_all_notes'):
notes |= (
self.exclude(author=user).annotate(owner_label=models.F('author__username'))
)
else:
notes |= (
self.exclude(author=user).filter(author__groups__name=settings.SUPER_LEXICOGRAPHS_GROUP_NAME)
.annotate(owner_label=models.Value(_('Super'), output_field=models.CharField()))
)
return notes
class Note(models.Model):
author = models.ForeignKey("auth.User", on_delete=models.PROTECT)
subject_ct = models.ForeignKey(ContentType, on_delete=models.PROTECT)
subject_id = models.PositiveIntegerField()
subject = GenericForeignKey('subject_ct', 'subject_id')
note = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
objects = models.Manager.from_queryset(NoteQuerySet)()
{% load i18n %}
<div class="border p-2">
<div id="note-form-template" class="d-none" data-url="{% url 'users:add_note' model='MODEL' pk='PK' %}">
<div class="mb-2 border-bottom pb-2">
<div class="clearfix">
<h6 class="float-left">{% trans 'Dodaj notatkę' %}</h6>
<a href="#" class="btn btn-xs btn-outline-dark float-right hide-note-form">&times;</a>
</div>
<div class="d-flex">
<textarea name="note" class="form-control mr-3"></textarea>
<a href="#" class="btn btn-sm btn-outline-dark ml-auto align-self-end add-note">{% trans 'Dodaj' %}</a>
</div>
</div>
</div>
<div class="note-form"></div>
<div>
<div class="mb-2">
<h6 class="mt-4 d-inline">{% trans 'Notatki' %}</h6>
<a href="#" class="show-note-form btn btn-xs btn-outline-dark ml-2">{% trans 'Dodaj' %}</a>
</div>
<table class="table table-sm table-striped border notes-table" data-url="{% url 'users:get_notes' model='MODEL' pk='PK' %}">
<tbody></tbody>
</table>
</div>
</div>
from django.contrib.auth import views as auth_views from django.contrib.auth import views as auth_views
from django.urls import include, path, reverse_lazy from django.urls import include, path, re_path, reverse_lazy
from django.views.generic import TemplateView from django.views.generic import TemplateView
from . import views from . import views
...@@ -54,4 +54,6 @@ urlpatterns = [ ...@@ -54,4 +54,6 @@ urlpatterns = [
), ),
name='password_reset_confirm' name='password_reset_confirm'
), ),
re_path(r'^notes/(?P<model>\w+.\w+)/(?P<pk>\w+)/$', views.get_notes, name='get_notes'),
re_path(r'^notes/(?P<model>\w+.\w+)/(?P<pk>\w+)/add/$', views.add_note, name='add_note'),
] ]
from django.apps import apps
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.shortcuts import get_current_site from django.contrib.sites.shortcuts import get_current_site
from django.http import JsonResponse
from django.shortcuts import get_object_or_404, render, redirect from django.shortcuts import get_object_or_404, render, redirect
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.decorators.http import require_http_methods
from users.forms import UserForm, UserProfileForm from common.decorators import ajax
from users.forms import UserForm, UserProfileForm, NoteForm
from users.models import Note
from users.utils import send_new_user_email from users.utils import send_new_user_email
...@@ -49,3 +55,32 @@ def user_edit(request, pk): ...@@ -49,3 +55,32 @@ def user_edit(request, pk):
else: else:
form = UserForm(instance=user) form = UserForm(instance=user)
return render(request, 'user_form.html', {'form': form, 'title': _('Edytuj użytkownika')}) return render(request, 'user_form.html', {'form': form, 'title': _('Edytuj użytkownika')})
@login_required
def get_notes(request, model, pk):
model = apps.get_model(*model.split('.'))
subject = get_object_or_404(model, pk=pk)
ct = ContentType.objects.get_for_model(model)
notes = Note.objects.filter(subject_ct=ct, subject_id=subject.pk).for_user(request.user).order_by('created_at')
return JsonResponse({
"notes": [{
"pk": note.pk,
"owner_label": note.owner_label,
"created_at": note.created_at,
"note": note.note,
} for note in notes],
})
@require_http_methods(["POST"])
@login_required
def add_note(request, model, pk):
model = apps.get_model(*model.split('.'))
subject = get_object_or_404(model, pk=pk)
note = Note(author=request.user, subject=subject)
form = NoteForm(instance=note, data=request.POST)
if form.is_valid():
form.save()
return JsonResponse({})
return JsonResponse(form.errors.get_json_data(), status=400)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment