diff options
Diffstat (limited to 'ishtar_common')
| -rw-r--r-- | ishtar_common/forms_common.py | 103 | ||||
| -rw-r--r-- | ishtar_common/models_imports.py | 30 | ||||
| -rw-r--r-- | ishtar_common/static/media/style.css | 9 | ||||
| -rw-r--r-- | ishtar_common/templates/blocks/readonly_input.html | 2 | ||||
| -rw-r--r-- | ishtar_common/templates/blocks/readonly_input_option.html | 1 | ||||
| -rw-r--r-- | ishtar_common/templates/ishtar/form.html | 3 | ||||
| -rw-r--r-- | ishtar_common/templates/ishtar/formset_import_match.html | 55 | ||||
| -rw-r--r-- | ishtar_common/views.py | 15 | ||||
| -rw-r--r-- | ishtar_common/widgets.py | 55 | 
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): | 
