summaryrefslogtreecommitdiff
path: root/ishtar_common
diff options
context:
space:
mode:
authorÉtienne Loks <etienne.loks@iggdrasil.net>2017-08-31 10:39:39 +0200
committerÉtienne Loks <etienne.loks@iggdrasil.net>2017-08-31 10:39:39 +0200
commit80e8827b0ff40ffa4c37985a6979fca6d7eba097 (patch)
treebb6acadd1f70294bdc1322a0e08aa51f650cfc5f /ishtar_common
parent14688dbd9435146b96b0743677579a622aa784ef (diff)
downloadIshtar-80e8827b0ff40ffa4c37985a6979fca6d7eba097.tar.bz2
Ishtar-80e8827b0ff40ffa4c37985a6979fca6d7eba097.zip
Target key forms: choose whom to associated with (refs #3725)
Diffstat (limited to 'ishtar_common')
-rw-r--r--ishtar_common/forms_common.py103
-rw-r--r--ishtar_common/models_imports.py30
-rw-r--r--ishtar_common/static/media/style.css9
-rw-r--r--ishtar_common/templates/blocks/readonly_input.html2
-rw-r--r--ishtar_common/templates/blocks/readonly_input_option.html1
-rw-r--r--ishtar_common/templates/ishtar/form.html3
-rw-r--r--ishtar_common/templates/ishtar/formset_import_match.html55
-rw-r--r--ishtar_common/views.py15
-rw-r--r--ishtar_common/widgets.py55
9 files changed, 253 insertions, 20 deletions
diff --git a/ishtar_common/forms_common.py b/ishtar_common/forms_common.py
index 4d14e4544..42e390925 100644
--- a/ishtar_common/forms_common.py
+++ b/ishtar_common/forms_common.py
@@ -27,7 +27,7 @@ from django.contrib.auth.models import User
from django.core import validators
from django.core.exceptions import ObjectDoesNotExist
from django.forms.formsets import formset_factory
-from django.forms.models import BaseModelFormSet
+from django.forms.models import BaseModelFormSet, BaseFormSet
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
@@ -103,10 +103,26 @@ class NewItemForm(forms.Form):
class NewImportForm(forms.ModelForm):
+ error_css_class = 'error'
+ required_css_class = 'required'
+
class Meta:
model = models.Import
- fields = ('name', 'importer_type', 'imported_file', 'imported_images',
- 'conservative_import', 'encoding', 'skip_lines')
+ fields = (
+ 'name', 'importer_type', 'imported_file', 'imported_images',
+ 'associated_group', 'conservative_import', 'encoding', 'skip_lines')
+
+ def __init__(self, *args, **kwargs):
+ user = kwargs.pop('user')
+ super(NewImportForm, self).__init__(*args, **kwargs)
+ groups = models.TargetKeyGroup.objects.filter(available=True)
+ if not user.is_superuser:
+ groups = groups.filter(all_user_can_use=True)
+ if not groups.count():
+ self.fields.pop('associated_group')
+ else:
+ self.fields['associated_group'].choices = [(None, '--')] + \
+ [(g.pk, unicode(g)) for g in groups.all()]
def clean(self):
data = self.cleaned_data
@@ -131,21 +147,47 @@ class TargetKeyForm(forms.ModelForm):
'key': forms.TextInput(attrs={'readonly': 'readonly'}),
'value': forms.Select(),
}
+ target = widgets.SelectReadonlyField(
+ model=models.ImportTarget, label=_(u"Target"))
+ remember = forms.ChoiceField(label=_(u"Remember"), choices=[],
+ required=False)
+ NULL_VALUE = '<NONE>'
def __init__(self, *args, **kwargs):
+ self.user = kwargs.pop('user')
super(TargetKeyForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
self.associated_import = None
if instance and instance.pk:
self.associated_import = instance.associated_import
- self.fields['target'].widget.attrs['readonly'] = True
+ self.fields['target'].choices = [(instance.target.pk,
+ instance.target.verbose_name)]
self.fields['key'].widget.attrs['readonly'] = True
self.fields['key'].widget.attrs['title'] = unicode(instance)
- self.fields['value'].widget.choices = list(
- instance.target.get_choices())
+ self.fields['value'].widget.choices = list(
+ instance.target.get_choices())
+ self.fields['value'].widget.choices.insert(
+ 1, (self.NULL_VALUE, _(u"Set to NULL")))
+
self.fields['key'].required = False
+ self.fields['target'].required = False
self.fields['value'].required = False
+ choices = [
+ ('import', _(u"this import only")),
+ ('me', _(u"me")),
+ ]
+ if self.associated_import and self.associated_import.associated_group \
+ and (self.associated_import.associated_group.all_user_can_modify
+ or self.user.is_superuser):
+ choices += [
+ ('group', unicode(_(u"the current group: {}")).format(
+ self.associated_import.associated_group))]
+ if self.user.is_superuser:
+ choices += [('all', _("all users"))]
+ self.fields['remember'].choices = choices
+ self.fields['remember'].widget.attrs['class'] = 'auto'
+
def clean_target(self):
instance = getattr(self, 'instance', None)
if instance and instance.pk:
@@ -162,10 +204,49 @@ class TargetKeyForm(forms.ModelForm):
def save(self, commit=True):
super(TargetKeyForm, self).save(commit)
- if self.cleaned_data.get('value'):
- self.instance.is_set = True
+ if not self.cleaned_data.get('value') or not self.user:
+ return
+ if self.cleaned_data['value'] == self.NULL_VALUE:
+ self.instance.value = None
+ self.instance.is_set = True
+
+ can_edit_group = \
+ self.associated_import and \
+ self.associated_import.associated_group and \
+ (self.associated_import.associated_group.all_user_can_modify
+ or self.user.is_superuser)
+
+ remember = self.cleaned_data.get('remember')
+ if remember == 'import' and self.associated_import:
self.instance.associated_import = self.associated_import
- self.instance.save()
+ self.instance.associated_user = None
+ self.instance.associated_group = None
+ elif remember == 'group' and can_edit_group:
+ self.instance.associated_import = None
+ self.instance.associated_user = None
+ self.instance.associated_group = \
+ self.associated_import.associated_group
+ elif remember == 'all' and self.user.is_superuser:
+ self.instance.associated_import = None
+ self.instance.associated_user = None
+ self.instance.associated_group = None
+ else:
+ # for me!
+ self.instance.associated_import = None
+ self.instance.associated_user = self.user.ishtaruser
+ self.instance.associated_group = None
+ self.instance.save()
+
+
+class TargetKeyFormset(BaseModelFormSet):
+ def __init__(self, *args, **kwargs):
+ self.user = kwargs.pop('user')
+ super(TargetKeyFormset, self).__init__(*args, **kwargs)
+
+ def get_form_kwargs(self, index):
+ kwargs = super(TargetKeyFormset, self).get_form_kwargs(index)
+ kwargs['user'] = self.user
+ return kwargs
class OrganizationForm(ManageOldType, NewItemForm):
@@ -698,9 +779,9 @@ class MergeFormSet(BaseModelFormSet):
return super(BaseModelFormSet, self)._construct_form(i, **kwargs)
def get_restricted_queryset(self):
- '''
+ """
Filter (from, to) when (to, from) is already here
- '''
+ """
q = self.queryset
if self._cached_list:
return self._cached_list
diff --git a/ishtar_common/models_imports.py b/ishtar_common/models_imports.py
index 852e9fe3a..6a313a95d 100644
--- a/ishtar_common/models_imports.py
+++ b/ishtar_common/models_imports.py
@@ -35,6 +35,7 @@ from django.core.files.base import ContentFile
from django.db.models.base import ModelBase
from django.db.models.signals import pre_delete
from django.template.defaultfilters import slugify
+from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _, pgettext_lazy
from ishtar_common.utils import create_slug, \
@@ -462,6 +463,14 @@ class ImportTarget(models.Model):
def __unicode__(self):
return self.target[:50] if self.target else self.comment
+ @cached_property
+ def verbose_name(self):
+ if not self.column.description:
+ return self.target[:50]
+ desc = self.column.description
+ desc = desc[0].lower() + desc[1:]
+ return u"{} - {}".format(self.target[:50], desc)
+
def natural_key(self):
return self.column.importer_type.slug, self.column.col_number, \
self.target
@@ -518,8 +527,9 @@ class TargetKey(models.Model):
Also temporary used for GeneralType to point missing link before adding
them in ItemKey table.
A targetkey connection can be create to be applied to one particular
- import (associated_import), one particular user (associated_user) or to all
- imports (associated_import and associated_user are empty).
+ import (associated_import), one particular user (associated_user),
+ one particular group (associated_group) or to all imports
+ (associated_import, associated_user and associated_group are empty).
"""
target = models.ForeignKey(ImportTarget, related_name='keys')
key = models.TextField(_(u"Key"))
@@ -534,6 +544,7 @@ class TargetKey(models.Model):
'associated_import',)
verbose_name = _(u"Importer - Target key")
verbose_name_plural = _(u"Importer - Targets keys")
+ ordering = ('target', 'key')
def __unicode__(self):
return u" - ".join([unicode(self.target), self.key[:50]])
@@ -740,18 +751,23 @@ ENCODINGS = [(settings.ENCODING, settings.ENCODING),
class Import(models.Model):
user = models.ForeignKey('IshtarUser')
- name = models.CharField(_(u"Name"), max_length=500,
- blank=True, null=True)
+ name = models.CharField(_(u"Name"), max_length=500, null=True)
importer_type = models.ForeignKey(ImporterType)
imported_file = models.FileField(
_(u"Imported file"), upload_to="upload/imports/%Y/%m/", max_length=220)
imported_images = models.FileField(
_(u"Associated images (zip file)"), upload_to="upload/imports/%Y/%m/",
blank=True, null=True, max_length=220)
- associated_group = models.ForeignKey(TargetKeyGroup, blank=True, null=True)
+ associated_group = models.ForeignKey(
+ TargetKeyGroup, blank=True, null=True,
+ help_text=_(u"If a group is selected, target key saved in this group "
+ u"will be used.")
+ )
encoding = models.CharField(_(u"Encoding"), choices=ENCODINGS,
default=u'utf-8', max_length=15)
- skip_lines = models.IntegerField(_(u"Skip lines"), default=1)
+ skip_lines = models.IntegerField(
+ _(u"Skip lines"), default=1,
+ help_text=_(u"Number of header lines in your file (can be 0)."))
error_file = models.FileField(_(u"Error file"),
upload_to="upload/imports/%Y/%m/",
blank=True, null=True, max_length=255)
@@ -765,7 +781,7 @@ class Import(models.Model):
default=u'C')
conservative_import = models.BooleanField(
_(u"Conservative import"), default=False,
- help_text='If set to true, do not overload existing values')
+ help_text=_(u'If set to true, do not overload existing values.'))
creation_date = models.DateTimeField(
_(u"Creation date"), auto_now_add=True, blank=True, null=True)
end_date = models.DateTimeField(_(u"End date"), blank=True,
diff --git a/ishtar_common/static/media/style.css b/ishtar_common/static/media/style.css
index 468b6557e..4f0330502 100644
--- a/ishtar_common/static/media/style.css
+++ b/ishtar_common/static/media/style.css
@@ -217,6 +217,10 @@ select{
width:370px;
}
+.form select.auto{
+ width: auto;
+}
+
/*label{display:block}*/
label:first-letter {
@@ -858,6 +862,11 @@ ul.form-flex li li label {
text-align:left;
}
+.helptext{
+ font-style: italic;
+ font-size: 0.9em;
+}
+
.help_text{
display:none;
font-size:0.9em;
diff --git a/ishtar_common/templates/blocks/readonly_input.html b/ishtar_common/templates/blocks/readonly_input.html
new file mode 100644
index 000000000..41a0c1305
--- /dev/null
+++ b/ishtar_common/templates/blocks/readonly_input.html
@@ -0,0 +1,2 @@
+{% for group_name, group_choices, group_index in widget.optgroups %}{% for option in group_choices %}
+ {% include option.template_name with widget=option %}{% endfor %}{% endfor %}
diff --git a/ishtar_common/templates/blocks/readonly_input_option.html b/ishtar_common/templates/blocks/readonly_input_option.html
new file mode 100644
index 000000000..b4ac473f0
--- /dev/null
+++ b/ishtar_common/templates/blocks/readonly_input_option.html
@@ -0,0 +1 @@
+{% if widget.attrs.selected %}<input type="hidden" name="{{ widget.name }}"{% if widget.value != None %} value="{{ widget.value.0 }}"{% endif %}{% include "django/forms/widgets/attrs.html" %} />{% endif %}
diff --git a/ishtar_common/templates/ishtar/form.html b/ishtar_common/templates/ishtar/form.html
index 539bd0408..236818c66 100644
--- a/ishtar_common/templates/ishtar/form.html
+++ b/ishtar_common/templates/ishtar/form.html
@@ -5,6 +5,9 @@
<div class='form'>
<form enctype="multipart/form-data" action="." method="post"{% if confirm %}
onsubmit='return confirm("{{confirm}}");'{% endif %}>{% csrf_token %}
+ {% for error in form.non_field_errors %}
+ <p>{{ error }}</p>
+ {% endfor %}
<table>
{{form}}
</table>
diff --git a/ishtar_common/templates/ishtar/formset_import_match.html b/ishtar_common/templates/ishtar/formset_import_match.html
new file mode 100644
index 000000000..f37938771
--- /dev/null
+++ b/ishtar_common/templates/ishtar/formset_import_match.html
@@ -0,0 +1,55 @@
+{% extends "base.html" %}
+{% load i18n %}
+{% block content %}
+<h2>{{page_name}}</h2>
+<p><a href="{% url 'current_imports' %}">{% trans "Back to import list" %}</a></p>
+<div class='form' id='global-vars'>
+<form action="." method="post">{% csrf_token %}
+
+ {% if extra_formset.non_form_errors %}
+ <div class='errors'>{{extra_formset.non_form_errors.as_ul}}</div>{% endif %}
+
+ {{ formset.management_form }}
+ {% for hidden in frm.hidden_fields %} {{ hidden }} {% endfor %}
+
+ <div id="{{formset.prefix}}">
+ {% for frm in formset %}{% ifchanged frm.target.value %}
+ {# <table> #}{% if not forloop.first%}</table>{% endif %}
+ <table class='clean-table'>
+ <caption>{{ frm.target.field.choices.0.1 }}</caption>
+ <tr>
+ <th>{% trans "Key" %}</th>
+ <th>{% trans "Value" %}</th>
+ <th>{% trans "For" %}</th>
+ </tr>
+ {% endifchanged %}
+ <tr>
+ <td>
+ {{frm.id}}
+ {{frm.target}}
+ {{frm.key}}
+ </td>
+ <td>
+ {{frm.value}}
+ </td>
+ <td>
+ {{frm.remember}}
+ </td>
+ </tr>
+ {% if forloop.last %}
+ </table>{% endif %}
+ {% endfor %}
+
+ {% if frm.errors %}<ul>{% for error in frm.errors.values %}
+ <li>{{error}}</li>{% endfor%}</ul>{% endif %}
+ {% for field in frm %}
+ {{field}}
+ {%endfor%}
+
+ </div>
+
+<input type="submit" value="{% trans 'Validate' %}"/>
+</form>
+</div>
+<p><a href="{% url 'current_imports' %}">{% trans "Back to import list" %}</a></p>
+{% endblock %}
diff --git a/ishtar_common/views.py b/ishtar_common/views.py
index d56165bf4..d2ff49a36 100644
--- a/ishtar_common/views.py
+++ b/ishtar_common/views.py
@@ -1663,6 +1663,11 @@ class NewImportView(IshtarMixin, LoginRequiredMixin, CreateView):
def get_success_url(self):
return reverse('current_imports')
+ def get_form_kwargs(self):
+ kwargs = super(NewImportView, self).get_form_kwargs()
+ kwargs['user'] = self.request.user
+ return kwargs
+
def form_valid(self, form):
user = models.IshtarUser.objects.get(pk=self.request.user.pk)
self.object = form.save(user=user)
@@ -1723,18 +1728,24 @@ class ImportOldListView(ImportListView):
class ImportLinkView(IshtarMixin, LoginRequiredMixin, ModelFormSetView):
- template_name = 'ishtar/formset.html'
+ template_name = 'ishtar/formset_import_match.html'
model = models.TargetKey
page_name = _(u"Link unmatched items")
extra = 0
form_class = forms.TargetKeyForm
+ formset_class = forms.TargetKeyFormset
+
+ def get_formset_kwargs(self):
+ kwargs = super(ImportLinkView, self).get_formset_kwargs()
+ kwargs['user'] = self.request.user
+ return kwargs
def get_queryset(self):
return self.model.objects.filter(
is_set=False, associated_import=self.kwargs['pk'])
def get_success_url(self):
- return reverse('current_imports')
+ return reverse('import_link_unmatched', args=[self.kwargs['pk']])
class ImportDeleteView(IshtarMixin, LoginRequiredMixin, DeleteView):
diff --git a/ishtar_common/widgets.py b/ishtar_common/widgets.py
index eb68bac2e..f5d32586c 100644
--- a/ishtar_common/widgets.py
+++ b/ishtar_common/widgets.py
@@ -45,6 +45,61 @@ logger = logging.getLogger(__name__)
reverse_lazy = lazy(reverse, unicode)
+class SelectReadonly(forms.Select):
+ template_name = 'blocks/readonly_input.html'
+ option_template_name = 'blocks/readonly_input_option.html'
+
+ def __init__(self, attrs=None, choices=(), model=None, available=None):
+ super(SelectReadonly, self).__init__(attrs, choices)
+ self.available = available
+ self.model = model
+
+ def get_choices(self, value):
+ q = self.model.objects
+ if self.available:
+ q = q.filter(available=True)
+ if value:
+ q = q.filter(pk=value)
+ for i in q.all():
+ if hasattr(self.model, 'verbose_name'):
+ label = i.verbose_name
+ else:
+ label = unicode(i)
+ yield (i.pk, label)
+
+ def render(self, name, value, attrs=None, choices=()):
+ if value:
+ self.choices = list(self.get_choices(value))
+ value = self.choices[0][0]
+ return super(SelectReadonly, self).render(name, value, attrs)
+
+
+class SelectReadonlyField(forms.ChoiceField):
+ def __init__(self, choices=(), required=True, widget=None, label=None,
+ initial=None, help_text='', *args, **kwargs):
+ self.available = False
+ self.model = None
+ if 'model' in kwargs:
+ self.model = kwargs.pop('model')
+ if 'available' in kwargs:
+ self.available = kwargs.pop('available')
+ widget = SelectReadonly(model=self.model, available=self.available)
+ super(SelectReadonlyField, self).__init__(
+ choices, required, widget, label, initial, help_text, *args,
+ **kwargs)
+
+ def get_q(self):
+ q = self.model.objects
+ if self.available:
+ q = q.filter(available=True)
+ return q
+
+ def valid_value(self, value):
+ if not self.model:
+ return super(SelectReadonlyField, self).valid_value(value)
+ return bool(self.get_q().filter(pk=value).count())
+
+
class Select2Multiple(forms.SelectMultiple):
def __init__(self, attrs=None, choices=(), remote=None, model=None,
available=None):