Projet

Général

Profil

0001-datasource-for-csv-files-5896.patch

Serghei Mihai, 13 juillet 2015 10:40

Télécharger (16,2 ko)

Voir les différences:

Subject: [PATCH] datasource for csv files (#5896)

 passerelle/apps/csvdatasource/__init__.py          | 26 ++++++
 passerelle/apps/csvdatasource/admin.py             |  3 +
 passerelle/apps/csvdatasource/forms.py             | 16 ++++
 .../csvdatasource/locale/fr/LC_MESSAGES/django.po  | 67 +++++++++++++++
 .../apps/csvdatasource/migrations/0001_initial.py  | 31 +++++++
 .../apps/csvdatasource/migrations/__init__.py      |  0
 passerelle/apps/csvdatasource/models.py            | 98 ++++++++++++++++++++++
 .../csvdatasource/csvdatasource_detail.html        | 52 ++++++++++++
 passerelle/apps/csvdatasource/tests.py             |  3 +
 passerelle/apps/csvdatasource/urls.py              | 27 ++++++
 passerelle/apps/csvdatasource/views.py             | 45 ++++++++++
 passerelle/settings.py                             |  1 +
 passerelle/static/css/style.css                    |  3 +
 13 files changed, 372 insertions(+)
 create mode 100644 passerelle/apps/csvdatasource/__init__.py
 create mode 100644 passerelle/apps/csvdatasource/admin.py
 create mode 100644 passerelle/apps/csvdatasource/forms.py
 create mode 100644 passerelle/apps/csvdatasource/locale/fr/LC_MESSAGES/django.po
 create mode 100644 passerelle/apps/csvdatasource/migrations/0001_initial.py
 create mode 100644 passerelle/apps/csvdatasource/migrations/__init__.py
 create mode 100644 passerelle/apps/csvdatasource/models.py
 create mode 100644 passerelle/apps/csvdatasource/templates/csvdatasource/csvdatasource_detail.html
 create mode 100644 passerelle/apps/csvdatasource/tests.py
 create mode 100644 passerelle/apps/csvdatasource/urls.py
 create mode 100644 passerelle/apps/csvdatasource/views.py
passerelle/apps/csvdatasource/__init__.py
1
# passerelle.contrib.csvdatasource
2
# Copyright (C) 2015  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
import django.apps
18

  
19
class AppConfig(django.apps.AppConfig):
20
    name = 'csvdatasource'
21

  
22
    def get_after_urls(self):
23
        from . import urls
24
        return urls.urlpatterns
25

  
26
default_app_config = 'csvdatasource.AppConfig'
passerelle/apps/csvdatasource/admin.py
1
from django.contrib import admin
2

  
3
# Register your models here.
passerelle/apps/csvdatasource/forms.py
1
from django.utils.text import slugify
2
from django import forms
3

  
4
from .models import CsvDataSource
5

  
6

  
7
class CsvDataSourceForm(forms.ModelForm):
8

  
9
    class Meta:
10
        model = CsvDataSource
11
        exclude = ('slug', 'users')
12

  
13
    def save(self, commit=True):
14
        if not self.instance.slug:
15
            self.instance.slug = slugify(self.instance.title)
16
        return super(CsvDataSourceForm, self).save(commit=commit)
passerelle/apps/csvdatasource/locale/fr/LC_MESSAGES/django.po
1
# SOME DESCRIPTIVE TITLE.
2
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
# This file is distributed under the same license as the PACKAGE package.
4
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
#
6
#, fuzzy
7
msgid ""
8
msgstr ""
9
"Project-Id-Version: PACKAGE VERSION\n"
10
"Report-Msgid-Bugs-To: \n"
11
"POT-Creation-Date: 2015-07-13 03:36-0500\n"
12
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
"Language-Team: LANGUAGE <LL@li.org>\n"
15
"Language: \n"
16
"MIME-Version: 1.0\n"
17
"Content-Type: text/plain; charset=UTF-8\n"
18
"Content-Transfer-Encoding: 8bit\n"
19
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
20

  
21
#: models.py:23
22
msgid "Optional column titles"
23
msgstr "Titres optionnels des colonnes"
24

  
25
#: models.py:24
26
msgid "in \"[column1_title],[column2_title],...\" format"
27
msgstr "au format \"[titre_colonne1],[titre_colonne2],...\""
28

  
29
#: models.py:26
30
msgid "Cache duration in seconds"
31
msgstr "Durée du cache en secondes"
32

  
33
#: models.py:29
34
msgid "Data Sources"
35
msgstr "Source des données"
36

  
37
#: templates/csvdatasource/csvdatasource_detail.html:10
38
msgid "edit"
39
msgstr "modifier"
40

  
41
#: templates/csvdatasource/csvdatasource_detail.html:14
42
msgid "delete"
43
msgstr "supprimer"
44

  
45
#: templates/csvdatasource/csvdatasource_detail.html:26
46
msgid "Endpoints"
47
msgstr ""
48

  
49
#: templates/csvdatasource/csvdatasource_detail.html:28
50
msgid "Returning all file lines: "
51
msgstr "Retourne toutes les lignes du fichier: "
52

  
53
#: templates/csvdatasource/csvdatasource_detail.html:31
54
msgid "Returning all lines containing 'abc' in 'text' column if defined : "
55
msgstr ""
56
"Retourne toutes les lignes du fichier contenant 'abc' dans la colonne 'text' "
57
"si définie :"
58

  
59
#: templates/csvdatasource/csvdatasource_detail.html:40
60
msgid "Security"
61
msgstr ""
62

  
63
#: templates/csvdatasource/csvdatasource_detail.html:43
64
msgid ""
65
"Accessing the listings is open, but posting requests is limited to the "
66
"following API users:"
67
msgstr ""
passerelle/apps/csvdatasource/migrations/0001_initial.py
1
# -*- coding: utf-8 -*-
2
from __future__ import unicode_literals
3

  
4
from django.db import models, migrations
5

  
6

  
7
class Migration(migrations.Migration):
8

  
9
    dependencies = [
10
        ('base', '0001_initial'),
11
    ]
12

  
13
    operations = [
14
        migrations.CreateModel(
15
            name='CsvDataSource',
16
            fields=[
17
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
18
                ('title', models.CharField(max_length=50)),
19
                ('slug', models.SlugField()),
20
                ('description', models.TextField()),
21
                ('csv_file', models.FileField(upload_to=b'csv')),
22
                ('columns_titles', models.CharField(help_text='in "[column1_title],[column2_title],..." format', max_length=256, verbose_name='Optional column titles', blank=True)),
23
                ('cache_duration', models.IntegerField(default=600, verbose_name='Cache duration in seconds')),
24
                ('users', models.ManyToManyField(to='base.ApiUser', blank=True)),
25
            ],
26
            options={
27
                'verbose_name': 'CSV File',
28
            },
29
            bases=(models.Model,),
30
        ),
31
    ]
passerelle/apps/csvdatasource/models.py
1
import re
2
import csv
3
import logging
4

  
5
from django.db import models
6
from django.core.cache import cache
7
from django.utils.translation import ugettext_lazy as _
8
from django.core.urlresolvers import reverse
9

  
10
from passerelle.base.models import BaseResource
11

  
12
logger = logging.getLogger(__name__)
13

  
14
_CACHE_SENTINEL = object()
15

  
16
class CsvError(Exception):
17
    pass
18

  
19

  
20
class CsvDataSource(BaseResource):
21
    csv_file = models.FileField(upload_to='csv')
22
    columns_titles = models.CharField(max_length=256, blank=True,
23
                            verbose_name=_('Optional column titles'),
24
                            help_text=_('in "[column1_title],[column2_title],..." format'))
25
    cache_duration = models.IntegerField(default=600,
26
                            verbose_name=_('Cache duration in seconds'))
27

  
28

  
29
    category = _('Data Sources')
30

  
31
    class Meta:
32
        verbose_name = 'CSV File'
33

  
34
    @classmethod
35
    def get_verbose_name(cls):
36
        return cls._meta.verbose_name
37

  
38
    @classmethod
39
    def get_icon_class(cls):
40
        return 'grid'
41

  
42
    @classmethod
43
    def is_enabled(cls):
44
        return True
45

  
46
    @classmethod
47
    def get_add_url(cls):
48
        return reverse('csvdatasource-add')
49

  
50
    def get_edit_url(self):
51
        return reverse('csvdatasource-edit', kwargs={'slug': self.slug})
52

  
53
    def get_delete_url(self):
54
        return reverse('csvdatasource-delete', kwargs={'slug': self.slug})
55

  
56
    def get_absolute_url(self):
57
        return reverse('csvdatasource-detail', kwargs={'slug': self.slug})
58

  
59
    def has_cache(self):
60
        self.__content = cache.get(self.slug, _CACHE_SENTINEL)
61
        return self.__content is not _CACHE_SENTINEL
62

  
63
    def set_cache(self, data):
64
        cache.set(self.slug, data, self.cache_duration)
65

  
66
    @property
67
    def content(self):
68
        if self.has_cache():
69
            return cache.get(self.slug, _CACHE_SENTINEL)
70
        self.__content = self.csv_file.read()
71
        self.set_cache(self.__content)
72
        return self.__content
73

  
74
    def get_data(self, filter_criteria=None):
75

  
76
        def filter_row(row, filter_criteria):
77
            if 'text' in self.columns_titles:
78
                col = self.columns_titles.index('text')
79
                if filter_criteria not in unicode(row[col], 'utf-8'):
80
                    return False
81
            return True
82

  
83
        data = []
84
        dialect = csv.Sniffer().sniff(self.content[:1024])
85
        reader = csv.reader(self.content.splitlines(), dialect)
86

  
87
        if self.columns_titles:
88
            self.columns_titles = self.columns_titles.split(',')
89
        else:
90
            self.columns_titles = reader.next()
91

  
92
        for row in reader:
93
            if filter_criteria and not filter_row(row, filter_criteria):
94
                continue
95
            line = dict(zip(self.columns_titles, row))
96
            data.append(line)
97

  
98
        return data
passerelle/apps/csvdatasource/templates/csvdatasource/csvdatasource_detail.html
1
{% extends "passerelle/base.html" %}
2
{% load i18n passerelle %}
3

  
4

  
5

  
6
{% block appbar %}
7
<h2>CSV - {{ object.title }}</h2>
8

  
9
{% if perms.csvdatasource.change_csvdatasource %}
10
<a rel="popup" class="button" href="{% url 'csvdatasource-edit' slug=object.slug %}">{% trans 'edit' %}</a>
11
{% endif %}
12

  
13
{% if perms.csvdatasource.delete_csvdatasource %}
14
<a rel="popup" class="button" href="{% url 'csvdatasource-delete' slug=object.slug %}">{% trans 'delete' %}</a>
15
{% endif %}
16
{% endblock %}
17

  
18

  
19

  
20
{% block content %}
21
<p>
22
Data file : {{ object.csv_file.name }}
23
</p>
24

  
25
<div>
26
<h3>{% trans 'Endpoints' %}</h3>
27
<ul>
28
  <li>{% trans "Returning all file lines: "%}
29
    <a href="{% url "csvdatasource-data" slug=object.slug %}">{%  url "csvdatasource-data" slug=object.slug %}</a>
30
  </li>
31
  <li>{% trans "Returning all lines containing 'abc' in 'text' column if defined : "%}
32
    <a href="{% url "csvdatasource-data" slug=object.slug %}?q=abc">{%  url "csvdatasource-data" slug=object.slug %}?q=abc</a>
33
  </li>
34
</ul>
35
</div>
36

  
37

  
38
{% if perms.base.view_accessright %}
39
<div>
40
<h3>{% trans "Security" %}</h3>
41

  
42
<p>
43
{% trans 'Accessing the listings is open, but posting requests is limited to the following API users:' %}
44
</p>
45

  
46
{% access_rights_table resource=object permission='can_post_request' %}
47
{% endif %}
48

  
49
</div>
50

  
51
{% endblock %}
52

  
passerelle/apps/csvdatasource/tests.py
1
from django.test import TestCase
2

  
3
# Create your tests here.
passerelle/apps/csvdatasource/urls.py
1
from django.conf.urls import patterns, include, url
2
from django.contrib.auth.decorators import login_required
3

  
4
from passerelle.urls_utils import decorated_includes, required, app_enabled
5

  
6
from views import *
7

  
8
public_urlpatterns = patterns('',
9
    url(r'^(?P<slug>[\w,-]+)/$', CsvDataSourceDetailView.as_view(), name='csvdatasource-detail'),
10
    url(r'^(?P<slug>[\w,-]+)/data$', CsvDataView.as_view(), name='csvdatasource-data'),
11
)
12

  
13
management_urlpatterns = patterns('',
14
    url(r'^add$', CsvDataSourceCreateView.as_view(), name='csvdatasource-add'),
15
    url(r'^(?P<slug>[\w,-]+)/edit$', CsvDataSourceUpdateView.as_view(), name='csvdatasource-edit'),
16
    url(r'^(?P<slug>[\w,-]+)/delete$', CsvDataSourceDeleteView.as_view(), name='csvdatasource-delete'),
17
)
18

  
19

  
20
urlpatterns = required(
21
    app_enabled('csvdatasource'),
22
    patterns('',
23
        url(r'^csvdatasource/', include(public_urlpatterns)),
24
        url(r'^manage/csvdatasource/',
25
            decorated_includes(login_required, include(management_urlpatterns))),
26
    )
27
)
passerelle/apps/csvdatasource/views.py
1
import json
2

  
3
from django.core.urlresolvers import reverse
4
from django.views.generic.edit import CreateView, UpdateView, DeleteView
5
from django.views.generic.detail import DetailView, SingleObjectMixin
6
from django.views.generic.base import View
7

  
8
from passerelle.base.views import ResourceView
9
from passerelle.utils import to_json
10

  
11
from .models import CsvDataSource
12
from .forms import CsvDataSourceForm
13

  
14

  
15
class CsvDataSourceDetailView(DetailView):
16
    model = CsvDataSource
17

  
18

  
19
class CsvDataSourceCreateView(CreateView):
20
    model = CsvDataSource
21
    template_name = 'passerelle/manage/service_form.html'
22
    form_class = CsvDataSourceForm
23

  
24

  
25
class CsvDataSourceUpdateView(UpdateView):
26
    model = CsvDataSource
27
    template_name = 'passerelle/manage/service_form.html'
28
    form_class = CsvDataSourceForm
29

  
30

  
31
class CsvDataSourceDeleteView(DeleteView):
32
    model = CsvDataSource
33
    template_name = 'passerelle/manage/service_confirm_delete.html'
34

  
35
    def get_success_url(self):
36
        return reverse('manage-home')
37

  
38

  
39
class CsvDataView(View, SingleObjectMixin):
40
    model = CsvDataSource
41

  
42
    @to_json('api')
43
    def get(self, request, *args, **kwargs):
44
        obj = self.get_object()
45
        return obj.get_data(request.GET.get('q'))
passerelle/settings.py
108 108
    'concerto',
109 109
    'bdp',
110 110
    'base_adresse',
111
    'csvdatasource',
111 112
    # backoffice templates and static
112 113
    'gadjo',
113 114
)
passerelle/static/css/style.css
32 32

  
33 33
li.gis a { background-image: url(icons/icon-gis.png); }
34 34
li.gis a:hover { background-image: url(icons/icon-gis-hover.png); }
35

  
36
li.grid a { background-image: url(icons/icon-grid.png); }
37
li.grid a:hover { background-image: url(icons/icon-grid-hover.png); }
35
-