Projet

Général

Profil

0001-general-add-support-for-slot-assets-24453.patch

Frédéric Péters, 19 juin 2018 10:35

Télécharger (16,1 ko)

Voir les différences:

Subject: [PATCH] general: add support for slot assets (#24453)

 combo/apps/assets/migrations/0001_initial.py  | 24 +++++++
 combo/apps/assets/migrations/__init__.py      |  0
 combo/apps/assets/models.py                   | 21 ++++++
 .../templates/combo/manager_assets.html       | 15 +++-
 combo/apps/assets/templatetags/__init__.py    |  0
 combo/apps/assets/templatetags/assets.py      | 47 +++++++++++++
 combo/apps/assets/urls.py                     |  2 +
 combo/apps/assets/views.py                    | 70 +++++++++++++++++--
 combo/manager/static/css/combo.manager.css    |  1 +
 combo/settings.py                             |  3 +
 tests/test_manager.py                         | 37 ++++++++++
 tests/test_public_templatetags.py             | 28 ++++++++
 12 files changed, 240 insertions(+), 8 deletions(-)
 create mode 100644 combo/apps/assets/migrations/0001_initial.py
 create mode 100644 combo/apps/assets/migrations/__init__.py
 create mode 100644 combo/apps/assets/models.py
 create mode 100644 combo/apps/assets/templatetags/__init__.py
 create mode 100644 combo/apps/assets/templatetags/assets.py
combo/apps/assets/migrations/0001_initial.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.12 on 2018-06-12 11:42
3
from __future__ import unicode_literals
4

  
5
from django.db import migrations, models
6

  
7

  
8
class Migration(migrations.Migration):
9

  
10
    initial = True
11

  
12
    dependencies = [
13
    ]
14

  
15
    operations = [
16
        migrations.CreateModel(
17
            name='Asset',
18
            fields=[
19
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20
                ('key', models.CharField(max_length=128, unique=True)),
21
                ('asset', models.FileField(upload_to=b'assets')),
22
            ],
23
        ),
24
    ]
combo/apps/assets/models.py
1
# combo - content management system
2
# Copyright (C) 2017-2018  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
from django.db import models
18

  
19
class Asset(models.Model):
20
    key = models.CharField(max_length=128, unique=True)
21
    asset = models.FileField(upload_to='assets')
combo/apps/assets/templates/combo/manager_assets.html
34 34
  <table class="main">
35 35
    <thead>
36 36
      <tr>
37
        <th>{% trans "Filename" %}</th>
37
        <th>{% trans "Name" %}</th>
38 38
        <th>{% trans "Size" %}</th>
39 39
        <th></th>
40 40
        <th></th>
......
43 43
    <tbody>
44 44
      {% for asset in object_list %}
45 45
      <tr class="{{ asset.css_classes }}">
46
        <td><a href="{{ asset.src }}">{{ asset.filename }}</a></td>
47
        <td>{{ asset.size|filesizeformat }}</td>
46
        <td><a href="{{ asset.src }}">{{ asset.name }}</a></td>
47
        <td>{% if asset.size %}{{ asset.size|filesizeformat }}{% else %}-{% endif %}</td>
48 48
        <td class="image">{% if asset.is_image %}<img data-href="{{ asset.src }}" src="{{ asset.thumb }}"/>{% endif %}</td>
49 49
        <td class="actions">
50
          {% if asset.key %}{# theme asset #}
51
          <a href="{% url 'combo-manager-slot-asset-upload' key=asset.key %}"
52
             class="overwrite" rel="popup">{% trans 'Overwrite' %}</a>
53
          {% if asset.asset %}
54
          <a href="{% url 'combo-manager-slot-asset-delete' key=asset.key %}"
55
             class="delete" rel="popup">{% trans 'Delete' %}</a>
56
          {% endif %}
57
          {% else %}
50 58
          <a href="{% url 'combo-manager-asset-overwrite' %}?img={{asset.filepath|iriencode}}"
51 59
             class="overwrite" rel="popup">{% trans 'Overwrite' %}</a>
52 60
          <a href="{% url 'combo-manager-asset-delete' %}?img={{asset.filepath|iriencode}}"
53 61
             class="delete" rel="popup">{% trans 'Delete' %}</a>
62
          {% endif %}
54 63
        </td>
55 64
        </td>
56 65
      </tr>
combo/apps/assets/templatetags/assets.py
1
# combo - content management system
2
# Copyright (C) 2017-2018  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
from django import template
18
from django.db.models.fields.files import ImageFieldFile
19

  
20
from ..models import Asset
21

  
22
register = template.Library()
23

  
24

  
25
@register.simple_tag
26
def asset_url(*args):
27
    for asset in args:
28
        if isinstance(asset, ImageFieldFile):
29
            try:
30
                return asset.url
31
            except ValueError:  # no associated file
32
                continue
33
        if isinstance(asset, basestring):
34
            try:
35
                asset = Asset.objects.get(key=asset)
36
            except Asset.DoesNotExist:
37
                continue
38
        return asset.asset.url
39
    return ''
40

  
41

  
42
@register.assignment_tag
43
def get_asset(key):
44
    try:
45
        return Asset.objects.get(key=key)
46
    except Asset.DoesNotExist:
47
        return None
combo/apps/assets/urls.py
25 25
    url(r'^delete$', views.asset_delete, name='combo-manager-asset-delete'),
26 26
    url(r'^overwrite/$', views.asset_overwrite, name='combo-manager-asset-overwrite'),
27 27
    url(r'^upload/$', views.asset_upload, name='combo-manager-asset-upload'),
28
    url(r'^upload/(?P<key>[\w_:-]+)/$', views.slot_asset_upload, name='combo-manager-slot-asset-upload'),
29
    url(r'^delete/(?P<key>[\w_:-]+)/$', views.slot_asset_delete, name='combo-manager-slot-asset-delete'),
28 30
]
29 31

  
30 32
urlpatterns = [
combo/apps/assets/views.py
24 24
from django.views.generic import TemplateView, ListView, FormView
25 25

  
26 26
import ckeditor
27
from sorl.thumbnail.shortcuts import get_thumbnail
27 28

  
28 29
from .forms import AssetUploadForm
30
from .models import Asset
29 31

  
30 32

  
31
class Asset(object):
33
class CkEditorAsset(object):
32 34
    def __init__(self, filepath):
33 35
        self.filepath = filepath
34
        self.filename = os.path.basename(filepath)
36
        self.name = os.path.basename(filepath)
35 37
        self.src = ckeditor.utils.get_media_url(filepath)
36 38

  
39
    @classmethod
40
    def get_assets(cls, request):
41
        return [cls(x) for x in ckeditor.views.get_image_files(request.user)]
42

  
37 43
    def css_classes(self):
38 44
        extension = os.path.splitext(self.filepath)[-1].strip('.')
39 45
        if extension:
......
55 61
        return ckeditor.views.is_image(self.src)
56 62

  
57 63

  
64
class SlotAsset(object):
65
    def __init__(self, key=None, asset=None):
66
        self.key = key
67
        self.name = settings.COMBO_ASSET_SLOTS[key]['label']
68
        self.asset = asset
69

  
70
    def is_image(self):
71
        return bool(self.asset)
72

  
73
    def size(self):
74
        if self.asset:
75
            return os.stat(self.asset.asset.path).st_size
76
        return None
77

  
78
    def src(self):
79
        return self.asset.asset.url if self.asset else ''
80

  
81
    def thumb(self):
82
        return get_thumbnail(self.asset.asset, '75x75').url
83

  
84
    @classmethod
85
    def get_assets(cls):
86
        assets = dict([(x.key, x) for x in Asset.objects.all() if x.key in settings.COMBO_ASSET_SLOTS])
87
        for key, value in settings.COMBO_ASSET_SLOTS.items():
88
            yield cls(key, asset=assets.get(key))
89

  
90

  
58 91
class Assets(ListView):
59 92
    template_name = 'combo/manager_assets.html'
60 93
    paginate_by = 10
61 94

  
62 95
    def get_queryset(self):
63
        files = [Asset(x) for x in ckeditor.views.get_image_files(self.request.user)]
96
        files = list(SlotAsset.get_assets()) + CkEditorAsset.get_assets(self.request)
64 97
        q = self.request.GET.get('q')
65 98
        if q:
66
            files = [x for x in files if q.lower() in x.filename.lower()]
67
        files.sort(key=lambda x: getattr(x, 'filename'))
99
            files = [x for x in files if q.lower() in x.name.lower()]
100
        files.sort(key=lambda x: getattr(x, 'name'))
68 101
        return files
69 102

  
70 103
    def get_context_data(self, **kwargs):
......
142 175
        return redirect(reverse('combo-manager-assets'))
143 176

  
144 177
asset_delete = AssetDelete.as_view()
178

  
179

  
180
class SlotAssetUpload(FormView):
181
    form_class = AssetUploadForm
182
    template_name = 'combo/manager_asset_upload.html'
183
    success_url = reverse_lazy('combo-manager-assets')
184

  
185
    def form_valid(self, form):
186
        try:
187
            asset = Asset.objects.get(key=self.kwargs['key'])
188
        except Asset.DoesNotExist:
189
            asset = Asset(key=self.kwargs['key'])
190
        asset.asset = self.request.FILES['upload']
191
        asset.save()
192
        return super(SlotAssetUpload, self).form_valid(form)
193

  
194
slot_asset_upload = SlotAssetUpload.as_view()
195

  
196

  
197
class SlotAssetDelete(TemplateView):
198
    template_name = 'combo/manager_asset_confirm_delete.html'
199

  
200
    def post(self, request, *args, **kwargs):
201
        Asset.objects.filter(key=kwargs['key']).delete()
202
        return redirect(reverse('combo-manager-assets'))
203

  
204
slot_asset_delete = SlotAssetDelete.as_view()
combo/manager/static/css/combo.manager.css
199 199

  
200 200
#assets-browser #assets-listing table td.image {
201 201
	padding: 0;
202
	text-align: center;
202 203
}
203 204

  
204 205
#assets-browser #assets-listing table td.actions {
combo/settings.py
303 303
# default duration of notifications (in days)
304 304
COMBO_DEFAULT_NOTIFICATION_DURATION = 3
305 305

  
306
# predefined slots for assets
307
COMBO_ASSET_SLOTS = {}
308

  
306 309
# hide work-in-progress/experimental/whatever cells for now
307 310
BOOKING_CALENDAR_CELL_ENABLED = False
308 311
NEWSLETTERS_CELL_ENABLED = False
tests/test_manager.py
20 20

  
21 21
from combo.wsgi import application
22 22
from combo.data.models import Page, CellBase, TextCell, LinkCell, ConfigJsonCell, JsonCell, PageSnapshot
23
from combo.apps.assets.models import Asset
23 24
from combo.apps.family.models import FamilyInfosCell
24 25
from combo.apps.search.models import SearchCell
25 26

  
......
652 653
    resp = resp.form.submit()
653 654
    assert resp.body.count('<tr class="asset') == 2
654 655

  
656
def test_asset_slots_management(app, admin_user):
657
    app = login(app)
658

  
659
    assert Asset.objects.count() == 0
660

  
661
    with override_settings(COMBO_ASSET_SLOTS={'collectivity:banner': {'label': 'Banner'}}):
662
        resp = app.get('/manage/assets/')
663
        assert '>Banner<' in resp.body
664
        assert '>Delete<' not in resp.body
665

  
666
        resp = resp.click('Overwrite')
667
        resp.form['upload'] = Upload('test.png',
668
                base64.decodestring('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAgAABAADRWoApgAA\nAABJRU5ErkJggg=='),
669
                'image/png')
670
        resp = resp.form.submit().follow()
671
        assert 'test.png' in resp.body
672
        assert '>Delete<' in resp.body
673
        assert Asset.objects.filter(key='collectivity:banner').count() == 1
674

  
675
        # upload a new version of image
676
        resp = resp.click('Overwrite')
677
        resp.form['upload'] = Upload('test2.png',
678
                base64.decodestring('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAgAABAADRWoApgAA\nAABJRU5ErkJggg=='),
679
                'image/png')
680
        resp = resp.form.submit().follow()
681
        assert 'test2.png' in resp.body
682
        assert '>Delete<' in resp.body
683
        assert Asset.objects.filter(key='collectivity:banner').count() == 1
684

  
685
        resp = resp.click('Delete')
686
        resp = resp.form.submit().follow()
687
        assert '>Banner<' in resp.body
688
        assert '>Delete<' not in resp.body
689
        assert Asset.objects.filter(key='collectivity:banner').count() == 0
690

  
691

  
655 692
def test_menu_json(app, admin_user):
656 693
    app.get('/manage/menu.json', status=302)
657 694

  
tests/test_public_templatetags.py
1
from StringIO import StringIO
2

  
1 3
import pytest
2 4

  
5
from django.core.files import File
3 6
from django.template import Context, Template
7
from django.test import override_settings
4 8
from django.test.client import RequestFactory
5 9
from django.contrib.auth.models import User, Group, AnonymousUser
6 10

  
11
from combo.data.models import Page
12
from combo.apps.assets.models import Asset
13

  
7 14
pytestmark = pytest.mark.django_db
8 15

  
9 16

  
......
121 128
    t = Template('{% load combo %}{% regroup cities by country as country_list %}'
122 129
                 '{% for c in country_list|get_group:"USA" %}{{c.name}},{% endfor %}')
123 130
    assert t.render(context) == 'New York,Chicago,'
131

  
132
def test_asset_template_tags():
133
    with override_settings(COMBO_ASSET_SLOTS={'collectivity:banner': {'label': 'Banner'}}):
134
        t = Template('''{% load assets %}{% get_asset "collectivity:banner" as banner %}{% if banner %}BANNER{% endif %}''')
135
        assert t.render(Context()) == ''
136

  
137
        Asset(key='collectivity:banner', asset=File(StringIO('test'), 'test.png')).save()
138
        assert t.render(Context()) == 'BANNER'
139

  
140
        t = Template('''{% load assets %}{% asset_url "collectivity:banner" %}''')
141
        assert t.render(Context()) == '/media/assets/test.png'
142

  
143
        page = Page(title='Home', slug='index', template_name='standard')
144
        page.save()
145

  
146
        t = Template('''{% load assets %}{% asset_url page.picture "collectivity:banner" %}''')
147
        assert t.render(Context()) == '/media/assets/test.png'
148

  
149
        page.picture = File(StringIO('test'), 'test2.png')
150
        page.save()
151
        assert t.render(Context({'page': page})) == '/media/page-pictures/test2.png'
124
-