Projet

Général

Profil

0001-invoicing-import-export-regies-69322.patch

Emmanuel Cazenave, 21 septembre 2022 16:41

Télécharger (10,5 ko)

Voir les différences:

Subject: [PATCH] invoicing: import/export regies (#69322)

 lingo/invoicing/models.py                     |  32 ++++++
 .../lingo/invoicing/manager_import.html       |  22 ++++
 .../lingo/invoicing/manager_regie_list.html   |   9 ++
 lingo/invoicing/urls.py                       |   2 +
 lingo/invoicing/views.py                      | 101 +++++++++++++++++-
 tests/invoicing/test_manager.py               |  25 +++++
 6 files changed, 187 insertions(+), 4 deletions(-)
 create mode 100644 lingo/invoicing/templates/lingo/invoicing/manager_import.html
lingo/invoicing/models.py
14 14
# You should have received a copy of the GNU Affero General Public License
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17
import copy
18

  
17 19
from django.contrib.auth.models import Group
18 20
from django.db import models
19 21
from django.utils.text import slugify
......
22 24
from lingo.utils.misc import generate_slug
23 25

  
24 26

  
27
class RegieImportError(Exception):
28
    pass
29

  
30

  
25 31
class Regie(models.Model):
26 32
    label = models.CharField(_('Label'), max_length=150)
27 33
    slug = models.SlugField(_('Identifier'), max_length=160, unique=True)
......
52 58
    @property
53 59
    def base_slug(self):
54 60
        return slugify(self.label)
61

  
62
    def export_json(self):
63
        return {
64
            'label': self.label,
65
            'slug': self.slug,
66
            'description': self.description,
67
            'permissions': {
68
                'cashier': self.cashier_role.name if self.cashier_role else None,
69
            },
70
        }
71

  
72
    @classmethod
73
    def import_json(cls, data):
74
        data = copy.deepcopy(data)
75
        permissions = data.pop('permissions') or {}
76
        role_name = permissions.get('cashier')
77
        if role_name:
78
            try:
79
                data['cashier_role'] = Group.objects.get(name=role_name)
80
            except Group.DoesNotExists:
81
                raise RegieImportError('Missing role: %s' % role_name)
82
            except Group.MultipleObjectsReturned:
83
                raise RegieImportError('Multiple role exist with the name: %s' % role_name)
84

  
85
        regie, created = cls.objects.update_or_create(slug=data['slug'], defaults=data)
86
        return created, regie
lingo/invoicing/templates/lingo/invoicing/manager_import.html
1
{% extends "lingo/invoicing/manager_regie_list.html" %}
2
{% load i18n %}
3

  
4
{% block breadcrumb %}
5
{{ block.super }}
6
<a href="{% url 'lingo-manager-invoicing-regie-import' %}">{% trans 'Import' %}</a>
7
{% endblock %}
8

  
9
{% block appbar %}
10
<h2>{% trans "Import Regies" %}</h2>
11
{% endblock %}
12

  
13
{% block content %}
14
<form method="post" enctype="multipart/form-data">
15
  {% csrf_token %}
16
  {{ form.as_p }}
17
  <div class="buttons">
18
    <button class="submit-button">{% trans "Import" %}</button>
19
    <a class="cancel" href="{% url 'lingo-manager-invoicing-regie-list' %}">{% trans 'Cancel' %}</a>
20
  </div>
21
</form>
22
{% endblock %}
lingo/invoicing/templates/lingo/invoicing/manager_regie_list.html
4 4
{% block appbar %}
5 5
<h2>{% trans 'Regies' %}</h2>
6 6
<span class="actions">
7
  <a class="extra-actions-menu-opener"></a>
8
  <ul class="extra-actions-menu">
9
    <li>
10
      <a href="{% url 'lingo-manager-invoicing-regie-import' %}">{% trans 'Import' %}</a>
11
    </li>
12
    <li>
13
      <a href="{% url 'lingo-manager-invoicing-regie-export' %}">{% trans 'Export' %}</a>
14
    </li>
15
  </ul>
7 16
  <a rel="popup" href="{% url 'lingo-manager-invoicing-regie-add' %}">{% trans 'New regie' %}</a>
8 17
</span>
9 18
{% endblock %}
lingo/invoicing/urls.py
41 41
        views.regie_delete,
42 42
        name='lingo-manager-invoicing-regie-delete',
43 43
    ),
44
    path('regies/import/', views.regies_import, name='lingo-manager-invoicing-regie-import'),
45
    path('regies/export/', views.regies_export, name='lingo-manager-invoicing-regie-export'),
44 46
]
lingo/invoicing/views.py
14 14
# You should have received a copy of the GNU Affero General Public License
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17
from django.urls import reverse
18
from django.views.generic import CreateView, DeleteView, DetailView, ListView, TemplateView, UpdateView
19

  
20
from lingo.invoicing.models import Regie
17
import collections
18
import datetime
19
import json
20

  
21
from django.contrib import messages
22
from django.db import transaction
23
from django.http import HttpResponse
24
from django.urls import reverse, reverse_lazy
25
from django.utils.translation import ugettext_lazy as _
26
from django.utils.translation import ungettext
27
from django.views.generic import (
28
    CreateView,
29
    DeleteView,
30
    DetailView,
31
    FormView,
32
    ListView,
33
    TemplateView,
34
    UpdateView,
35
)
36

  
37
from lingo.invoicing.models import Regie, RegieImportError
38
from lingo.pricing.forms import ImportForm
39

  
40

  
41
def import_regies(data):
42
    results = collections.defaultdict(list)
43
    with transaction.atomic():
44
        regies = data.get('regies', [])
45
        for regie in regies:
46
            created, regie_obj = Regie.import_json(regie)
47
            if created:
48
                results['created'].append(regie_obj)
49
            else:
50
                results['updated'].append(regie_obj)
51
    return results
21 52

  
22 53

  
23 54
class HomeView(TemplateView):
......
81 112

  
82 113

  
83 114
regie_delete = RegieDeleteView.as_view()
115

  
116

  
117
class RegiesExportView(ListView):
118
    model = Regie
119

  
120
    def get(self, request, *args, **kwargs):
121
        response = HttpResponse(content_type='application/json')
122
        today = datetime.date.today()
123
        attachment = 'attachment; filename="export_regies_{}.json"'.format(today.strftime('%Y%m%d'))
124
        response['Content-Disposition'] = attachment
125
        json.dump({'regies': [regie.export_json() for regie in self.get_queryset()]}, response, indent=2)
126
        return response
127

  
128

  
129
regies_export = RegiesExportView.as_view()
130

  
131

  
132
class RegiesImportView(FormView):
133
    form_class = ImportForm
134
    template_name = 'lingo/invoicing/manager_import.html'
135
    success_url = reverse_lazy('lingo-manager-invoicing-regie-list')
136

  
137
    def form_valid(self, form):
138
        try:
139
            config_json = json.loads(self.request.FILES['config_json'].read())
140
        except ValueError:
141
            form.add_error('config_json', _('File is not in the expected JSON format.'))
142
            return self.form_invalid(form)
143

  
144
        try:
145
            results = import_regies(config_json)
146
        except RegieImportError as exc:
147
            form.add_error('config_json', '%s' % exc)
148
            return self.form_invalid(form)
149

  
150
        import_messages = {
151
            'create': lambda x: ungettext(
152
                'A regie was created.',
153
                '%(count)d regies were created.',
154
                x,
155
            ),
156
            'update': lambda x: ungettext(
157
                'A regie was updated.',
158
                '%(count)d regie were updated.',
159
                x,
160
            ),
161
        }
162
        create_message = _('No regie created.')
163
        update_message = _('No regie updated.')
164
        created = len(results.get('created', []))
165
        updated = len(results.get('updated', []))
166
        if created:
167
            create_message = import_messages.get('create')(created) % {'count': created}
168
        if updated:
169
            update_message = import_messages.get('update')(updated) % {'count': updated}
170
        message = "%s %s" % (create_message, update_message)
171
        messages.info(self.request, message)
172

  
173
        return super().form_valid(form)
174

  
175

  
176
regies_import = RegiesImportView.as_view()
tests/invoicing/test_manager.py
1
import json
1 2
from urllib.parse import urlparse
2 3

  
3 4
import pytest
4 5
from django.contrib.auth.models import Group
5 6
from django.urls import reverse
7
from webtest import Upload
6 8

  
7 9
from lingo.invoicing.models import Regie
8 10
from tests.utils import login
......
142 144
    response = resp.form.submit().follow()
143 145
    assert Regie.objects.count() == 0
144 146
    assert urlparse(response.request.url).path == reverse('lingo-manager-invoicing-regie-list')
147

  
148

  
149
def test_manager_invoicing_regie_import_export(app, admin_user, freezer):
150
    freezer.move_to('2020-06-15')
151
    app = login(app)
152
    group = Group.objects.create(name='role-foo')
153
    regie1 = Regie.objects.create(label='Foo', description='foo description', cashier_role=group)
154
    regie2 = Regie.objects.create(label='Bar', description='bar description', cashier_role=group)
155
    response = app.get(reverse('lingo-manager-invoicing-regie-export'))
156
    assert response.headers['content-type'] == 'application/json'
157
    assert response.headers['content-disposition'] == 'attachment; filename="export_regies_20200615.json"'
158
    regies_export = response.text
159
    regies_json = json.loads(regies_export)
160
    assert len(regies_json['regies']) == 2
161

  
162
    regie1.delete()
163
    assert Regie.objects.count() == 1
164
    response = app.get(reverse('lingo-manager-invoicing-regie-import'))
165
    response.form['config_json'] = Upload('export.json', regies_export.encode('utf-8'), 'application/json')
166
    response = response.form.submit().follow()
167
    assert urlparse(response.request.url).path == reverse('lingo-manager-invoicing-regie-list')
168
    assert 'A regie was created. A regie was updated.' in response.text
169
    assert Regie.objects.count() == 2
145
-