├── .github ├── FUNDING.yml └── workflows │ └── main.yml ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.rst ├── README.ru.rst ├── example_project ├── main │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── templates │ │ ├── base.html │ │ ├── index.html │ │ └── page.html │ ├── tests.py │ ├── urls.py │ └── views.py ├── manage.py ├── requirements.txt └── tildaexample │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── setup.py └── tilda ├── __init__.py ├── admin.py ├── api.py ├── fields.py ├── helpers.py ├── locale └── ru │ └── LC_MESSAGES │ ├── django.mo │ └── django.po ├── migrations ├── 0001_initial.py └── __init__.py ├── models.py └── templates └── tilda └── widget.html /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: 1vank1n 4 | patreon: 1vank1n 5 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the master branch 7 | on: 8 | push: 9 | tags: 10 | - '*' 11 | 12 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 13 | jobs: 14 | # This workflow contains a single job called "build" 15 | build: 16 | # The type of runner that the job will run on 17 | runs-on: ubuntu-latest 18 | 19 | # Steps represent a sequence of tasks that will be executed as part of the job 20 | steps: 21 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 22 | - uses: actions/checkout@v2 23 | 24 | - name: Create sdist 25 | run: python setup.py sdist 26 | 27 | - name: Publish package 28 | if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') 29 | uses: pypa/gh-action-pypi-publish@master 30 | with: 31 | user: __token__ 32 | password: ${{ secrets.pypi_password }} 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.pot 3 | *.pyc 4 | __pycache__/ 5 | .DS_Store 6 | .env 7 | dist/ 8 | media/ 9 | django_tilda.egg-info/ 10 | local_settings.py 11 | db.sqlite3 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Ivan Lukyanets 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include setup.py README.* MANIFEST.in LICENSE 2 | recursive-include tilda/locale * 3 | recursive-include tilda/migrations * 4 | recursive-include tilda/templates * 5 | global-exclude *.pyc 6 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Django Tilda 2 | ============ 3 | 4 | `На русском `_ 5 | 6 | |Downloads| 7 | 8 | .. |Downloads| image:: https://pepy.tech/badge/django-tilda 9 | :target: https://pepy.tech/project/django-tilda 10 | 11 | **Warning!** Before start you have to register in `tilda.cc`_ and have 12 | “Tilda Business” account for use Tilda API. 13 | 14 | Synchronization available only for **published** in Project pages. 15 | 16 | Supported versions 17 | ------------------ 18 | 19 | - Django >= 2.0 (old version supported >= 1.10) 20 | - Python 2.7, >=3.5 21 | 22 | Screenshots 23 | ----------- 24 | 25 | .. figure:: https://img-fotki.yandex.ru/get/518060/94968737.3/0_9cefa_18f3e324_orig 26 | :alt: Screenshot 27 | 28 | Screenshot 29 | 30 | Quick-Start Guide 31 | ----------------- 32 | 33 | 1. Install Django Tilda: 34 | 35 | :: 36 | 37 | pip install django-tilda 38 | 39 | 2. Add to your ``INSTALLED_APPS``: 40 | 41 | :: 42 | 43 | 'django_object_actions', 44 | 'tilda', 45 | 46 | 3. Add in ``settings.py`` params: 47 | 48 | *TILDA_PUBLIC_KEY* and *TILDA_SECRET_KEY* generated in Business account 49 | Tilda.cc — https://tilda.cc/identity/apikeys/ 50 | 51 | *TILDA_PROJECTID* — you need to have exist project in Tilda.cc (look at 52 | your location bar when you work with project in Tilda panel) 53 | 54 | *TILDA_MEDIA_IMAGES_URL* — your url path for folder in TILDA_MEDIA_IMAGES 55 | 56 | :: 57 | 58 | TILDA_PUBLIC_KEY = '' 59 | TILDA_SECRET_KEY = '' 60 | TILDA_PROJECTID = '' 61 | TILDA_MEDIA_IMAGES_URL = '/media/tilda/images' 62 | TILDA_MEDIA_IMAGES = os.path.join(BASE_DIR, 'media/tilda/images') 63 | TILDA_MEDIA_JS = os.path.join(BASE_DIR, 'media/tilda/js') 64 | TILDA_MEDIA_CSS = os.path.join(BASE_DIR, 'media/tilda/css') 65 | 66 | 4. *TILDA_MEDIA_IMAGES*, *TILDA_MEDIA_JS*, *TILDA_MEDIA_CSS* — create 67 | this folders manually 68 | 69 | 5. Migrate ``python manage.py migrate`` 70 | 71 | Done! 72 | 73 | Usage 74 | ----- 75 | 76 | Simple example: 77 | 78 | **models.py** 79 | 80 | .. code:: python 81 | 82 | from django.db import models 83 | from tilda import TildaPageField 84 | 85 | 86 | class Page(models.Model): 87 | 88 | title = models.CharField( 89 | u'Title', 90 | max_length=100 91 | ) 92 | 93 | tilda_content = TildaPageField( 94 | verbose_name=u'Tilda Page' 95 | ) 96 | 97 | created = models.DateTimeField( 98 | u'Created', 99 | auto_now_add=True 100 | ) 101 | 102 | **template** (``object`` — instance of Page class) 103 | 104 | .. code:: html 105 | 106 | 107 | ... 108 | {% for css in object.tilda_content.get_css_list %} 109 | 110 | {% endfor %} 111 | ... 112 | 113 | 114 | 115 | ... 116 | {{ object.tilda_content.html|safe }} 117 | ... 118 | {% for js in object.tilda_content.get_js_list %} 119 | 120 | {% endfor %} 121 | 122 | 123 | Localizations 124 | ------------- 125 | 126 | - English 127 | - Русский 128 | 129 | .. _tilda.cc: https://tilda.cc/?r=1614568 130 | -------------------------------------------------------------------------------- /README.ru.rst: -------------------------------------------------------------------------------- 1 | Django Tilda 2 | ============ 3 | 4 | `English readme `_ 5 | 6 | |Downloads| 7 | 8 | .. |Downloads| image:: https://pepy.tech/badge/django-tilda 9 | :target: https://pepy.tech/project/django-tilda 10 | 11 | **Внимание!** Перед тем как приступить к интеграции с `tilda.cc`_ убедитесь, что у Вас “Tilda Business” тариф. Только на нём доступно Tilda API. 12 | 13 | Синхронизация возможна только для **опубликованных** страниц Проекта. 14 | 15 | Поддерживаемые версии 16 | ------------------ 17 | 18 | - Django >= 2.0 (поддерживаются старые версии >= 1.10) 19 | - Python 2.7, >= 3.5 20 | 21 | Скриншот 22 | ----------- 23 | 24 | .. figure:: https://img-fotki.yandex.ru/get/518060/94968737.3/0_9cefa_18f3e324_orig 25 | :alt: Screenshot 26 | 27 | Screenshot 28 | 29 | Быстрый старт 30 | ----------------- 31 | 32 | 1. Устанавливаем Django Tilda: 33 | 34 | :: 35 | 36 | pip install django-tilda 37 | 38 | 2. Добавь в ``INSTALLED_APPS``: 39 | 40 | :: 41 | 42 | 'django_object_actions', 43 | 'tilda', 44 | 45 | 3. Также добавь в ``settings.py``: 46 | 47 | *TILDA_PUBLIC_KEY* и *TILDA_SECRET_KEY* генерируется на Tilda.cc — https://tilda.cc/identity/apikeys/ 48 | 49 | *TILDA_PROJECTID* — у Вас должен быть уже созданный проект на Tilda.cc (в адресной строке легко можно найти project_id) 50 | 51 | *TILDA_MEDIA_IMAGES_URL* — url до папки TILDA_MEDIA_IMAGES 52 | 53 | :: 54 | 55 | TILDA_PUBLIC_KEY = '' 56 | TILDA_SECRET_KEY = '' 57 | TILDA_PROJECTID = '' 58 | TILDA_MEDIA_IMAGES_URL = '/media/tilda/images' 59 | TILDA_MEDIA_IMAGES = os.path.join(BASE_DIR, 'media/tilda/images') 60 | TILDA_MEDIA_JS = os.path.join(BASE_DIR, 'media/tilda/js') 61 | TILDA_MEDIA_CSS = os.path.join(BASE_DIR, 'media/tilda/css') 62 | 63 | 4. *TILDA_MEDIA_IMAGES*, *TILDA_MEDIA_JS*, *TILDA_MEDIA_CSS* — создайте эти папки самостоятельно (это важно!) 64 | 65 | 5. Запустить миграцию ``python manage.py migrate`` 66 | 67 | Готово! 68 | 69 | Использование 70 | ----- 71 | 72 | Простой пример: 73 | 74 | **models.py** 75 | 76 | .. code:: python 77 | 78 | from django.db import models 79 | from tilda import TildaPageField 80 | 81 | 82 | class Page(models.Model): 83 | 84 | title = models.CharField( 85 | u'Title', 86 | max_length=100 87 | ) 88 | 89 | tilda_content = TildaPageField( 90 | verbose_name=u'Tilda Page' 91 | ) 92 | 93 | created = models.DateTimeField( 94 | u'Created', 95 | auto_now_add=True 96 | ) 97 | 98 | **template** (``object`` — экземпляр Page class) 99 | 100 | .. code:: html 101 | 102 | 103 | ... 104 | {% for css in object.tilda_content.get_css_list %} 105 | 106 | {% endfor %} 107 | ... 108 | 109 | 110 | 111 | ... 112 | {{ object.tilda_content.html|safe }} 113 | ... 114 | {% for js in object.tilda_content.get_js_list %} 115 | 116 | {% endfor %} 117 | 118 | 119 | Localizations 120 | ------------- 121 | 122 | - English 123 | - Русский 124 | 125 | .. _tilda.cc: https://tilda.cc/?r=1614568 126 | -------------------------------------------------------------------------------- /example_project/main/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1vank1n/django-tilda/3e4eaf0b8e63e6cbd717ea11d69b0d3025c15c9f/example_project/main/__init__.py -------------------------------------------------------------------------------- /example_project/main/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from . import models 3 | 4 | 5 | class PageAdmin(admin.ModelAdmin): 6 | list_display = ('title', 'tilda_content', 'created', ) 7 | list_filter = ('created', ) 8 | readonly_fields = ('created', ) 9 | search_fields = ('title', ) 10 | admin.site.register(models.Page, PageAdmin) 11 | -------------------------------------------------------------------------------- /example_project/main/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MainConfig(AppConfig): 5 | name = 'main' 6 | -------------------------------------------------------------------------------- /example_project/main/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.8 on 2017-12-25 14:40 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | import tilda.fields 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | initial = True 13 | 14 | dependencies = [ 15 | ('tilda', '0001_initial'), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='Page', 21 | fields=[ 22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 23 | ('title', models.CharField(max_length=100, verbose_name='Title')), 24 | ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), 25 | ('tilda_content', tilda.fields.TildaPageField(on_delete=django.db.models.deletion.CASCADE, to='tilda.TildaPage', verbose_name='Tilda Page')), 26 | ], 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /example_project/main/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1vank1n/django-tilda/3e4eaf0b8e63e6cbd717ea11d69b0d3025c15c9f/example_project/main/migrations/__init__.py -------------------------------------------------------------------------------- /example_project/main/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.core.urlresolvers import reverse 3 | from tilda import TildaPageField 4 | 5 | 6 | class Page(models.Model): 7 | 8 | title = models.CharField( 9 | u'Title', 10 | max_length=100 11 | ) 12 | 13 | tilda_content = TildaPageField( 14 | verbose_name=u'Tilda Page' 15 | ) 16 | 17 | created = models.DateTimeField( 18 | u'Created', 19 | auto_now_add=True 20 | ) 21 | 22 | def get_absolute_url(self): 23 | return reverse('main:page_detail', kwargs={'pk': self.pk}) 24 | -------------------------------------------------------------------------------- /example_project/main/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {% block head %}{% endblock head %} 8 | 9 | 10 | {% block content %}{% endblock content %} 11 | 12 | {% block footer_js %}{% endblock footer_js %} 13 | 14 | 15 | -------------------------------------------------------------------------------- /example_project/main/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | 4 | {% block content %} 5 | 16 | {% endblock content %} 17 | -------------------------------------------------------------------------------- /example_project/main/templates/page.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | 4 | {% block head %} 5 | {% for css in object.tilda_content.get_css_list %} 6 | 7 | {% endfor %} 8 | {% endblock head %} 9 | 10 | 11 | {% block content %} 12 |

{{ object.tilda_content.html|safe }}

13 | {% endblock content %} 14 | 15 | 16 | {% block footer_js %} 17 | {% for js in object.tilda_content.get_js_list %} 18 | 19 | {% endfor %} 20 | {% endblock footer_js %} 21 | -------------------------------------------------------------------------------- /example_project/main/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /example_project/main/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | from . import views 3 | 4 | urlpatterns = [ 5 | url(r'^$', 6 | views.IndexView.as_view(), 7 | name='index'), 8 | 9 | url(r'^(?P[-\w]+)/$', 10 | views.PageDetailView.as_view(), 11 | name='page_detail'), 12 | ] 13 | -------------------------------------------------------------------------------- /example_project/main/views.py: -------------------------------------------------------------------------------- 1 | from django.views.generic import (TemplateView, DetailView) 2 | from . import models 3 | 4 | 5 | class IndexView(TemplateView): 6 | template_name = 'index.html' 7 | 8 | def get_context_data(self, **kwargs): 9 | context = super(IndexView, self).get_context_data(**kwargs) 10 | context['page_list'] = models.Page.objects.all() 11 | return context 12 | 13 | 14 | class PageDetailView(DetailView): 15 | template_name = 'page.html' 16 | model = models.Page 17 | -------------------------------------------------------------------------------- /example_project/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tildaexample.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /example_project/requirements.txt: -------------------------------------------------------------------------------- 1 | bleach==3.3.0 2 | certifi==2024.7.4 3 | chardet==3.0.4 4 | docutils==0.14 5 | idna==3.7 6 | pkginfo==1.5.0.1 7 | Pygments==2.15.0 8 | readme-renderer==24.0 9 | requests==2.32.4 10 | requests-toolbelt==0.9.1 11 | six==1.12.0 12 | tqdm==4.66.3 13 | twine==1.13.0 14 | urllib3==1.26.19 15 | webencodings==0.5.1 16 | -------------------------------------------------------------------------------- /example_project/tildaexample/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1vank1n/django-tilda/3e4eaf0b8e63e6cbd717ea11d69b0d3025c15c9f/example_project/tildaexample/__init__.py -------------------------------------------------------------------------------- /example_project/tildaexample/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for tildaexample project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.11.1. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.11/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = '199+%(gjiu+j-s54)3)5z9g#ekb9yrj=%bn7))c5wu$qthnf&=' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 41 | 'django_object_actions', 42 | 'tilda', 43 | 44 | 'main', 45 | ] 46 | 47 | MIDDLEWARE = [ 48 | 'django.middleware.security.SecurityMiddleware', 49 | 'django.contrib.sessions.middleware.SessionMiddleware', 50 | 'django.middleware.common.CommonMiddleware', 51 | 'django.middleware.csrf.CsrfViewMiddleware', 52 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 53 | 'django.contrib.messages.middleware.MessageMiddleware', 54 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 55 | ] 56 | 57 | ROOT_URLCONF = 'tildaexample.urls' 58 | 59 | TEMPLATES = [ 60 | { 61 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 62 | 'DIRS': [], 63 | 'APP_DIRS': True, 64 | 'OPTIONS': { 65 | 'context_processors': [ 66 | 'django.template.context_processors.debug', 67 | 'django.template.context_processors.request', 68 | 'django.contrib.auth.context_processors.auth', 69 | 'django.contrib.messages.context_processors.messages', 70 | ], 71 | }, 72 | }, 73 | ] 74 | 75 | WSGI_APPLICATION = 'tildaexample.wsgi.application' 76 | 77 | 78 | # Database 79 | # https://docs.djangoproject.com/en/1.11/ref/settings/#databases 80 | 81 | DATABASES = { 82 | 'default': { 83 | 'ENGINE': 'django.db.backends.sqlite3', 84 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 85 | } 86 | } 87 | 88 | 89 | # Password validation 90 | # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators 91 | 92 | # AUTH_PASSWORD_VALIDATORS = [ 93 | # { 94 | # 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 95 | # }, 96 | # { 97 | # 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 98 | # }, 99 | # { 100 | # 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 101 | # }, 102 | # { 103 | # 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 104 | # }, 105 | # ] 106 | 107 | 108 | # Internationalization 109 | # https://docs.djangoproject.com/en/1.11/topics/i18n/ 110 | 111 | LANGUAGE_CODE = 'en-us' 112 | 113 | TIME_ZONE = 'UTC' 114 | 115 | USE_I18N = True 116 | 117 | USE_L10N = True 118 | 119 | USE_TZ = True 120 | 121 | 122 | # Static files (CSS, JavaScript, Images) 123 | # https://docs.djangoproject.com/en/1.11/howto/static-files/ 124 | 125 | STATIC_URL = '/static/' 126 | 127 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media') 128 | MEDIA_URL = '/media/' 129 | 130 | 131 | # Tilda fields 132 | # Integration with tilda.cc 133 | 134 | TILDA_PUBLIC_KEY = '' 135 | TILDA_SECRET_KEY = '' 136 | TILDA_PROJECTID = '' 137 | TILDA_MEDIA_IMAGES_URL = '/media/tilda/images' 138 | TILDA_MEDIA_IMAGES = os.path.join(BASE_DIR, 'media/tilda/images') 139 | TILDA_MEDIA_JS = os.path.join(BASE_DIR, 'media/tilda/js') 140 | TILDA_MEDIA_CSS = os.path.join(BASE_DIR, 'media/tilda/css') 141 | 142 | 143 | try: 144 | from .local_settings import * 145 | except ImportError: 146 | pass 147 | -------------------------------------------------------------------------------- /example_project/tildaexample/urls.py: -------------------------------------------------------------------------------- 1 | """tildaexample URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.11/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls import (url, include) 17 | from django.contrib import admin 18 | from django.conf.urls.static import static 19 | from django.conf import settings 20 | from django.contrib.staticfiles.urls import staticfiles_urlpatterns 21 | 22 | 23 | urlpatterns = [ 24 | url(r'^admin/', admin.site.urls), 25 | 26 | url(r'^', 27 | include('main.urls', namespace='main')), 28 | ] 29 | 30 | urlpatterns += staticfiles_urlpatterns() + \ 31 | static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 32 | -------------------------------------------------------------------------------- /example_project/tildaexample/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for tildaexample project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tildaexample.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | 4 | setup( 5 | name='django-tilda', 6 | version='1.0.14', 7 | author='Ivan Lukyanets', 8 | author_email='lukyanets.ivan@gmail.com', 9 | url='https://github.com/1vank1n/django-tilda', 10 | packages=[ 11 | 'tilda', 12 | 'tilda.locale', 13 | ], 14 | include_package_data=True, 15 | license='MIT', 16 | description='A Django app for fetch/download pages from API Tilda.cc', 17 | keywords='django tilda', 18 | long_description=open('README.rst').read(), 19 | install_requires=[ 20 | 'django-object-actions==0.10.0', 21 | 'requests==2.32.2', 22 | ], 23 | classifiers=[ 24 | 'Development Status :: 4 - Beta', 25 | 'Framework :: Django', 26 | 'Intended Audience :: Developers', 27 | 'License :: OSI Approved :: MIT License', 28 | 'Operating System :: OS Independent', 29 | 'Programming Language :: Python :: 3', 30 | 'Programming Language :: Python :: 3.3', 31 | 'Programming Language :: Python :: 3.4', 32 | 'Programming Language :: Python :: 3.6', 33 | ], 34 | ) 35 | -------------------------------------------------------------------------------- /tilda/__init__.py: -------------------------------------------------------------------------------- 1 | from .fields import TildaPageField 2 | -------------------------------------------------------------------------------- /tilda/admin.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from django.conf import settings 3 | from django.contrib import (admin, messages) 4 | from django_object_actions import DjangoObjectActions 5 | from django.utils.translation import ugettext as _ 6 | 7 | from . import api 8 | from . import models 9 | 10 | 11 | class TildaPageAdmin(DjangoObjectActions, admin.ModelAdmin): 12 | list_display = ('title', 'id', 'synchronized', 'created', ) 13 | list_filter = ('synchronized', 'created', ) 14 | search_fields = ('title', 'id', ) 15 | readonly_fields = ( 16 | 'id', 17 | 'title', 18 | 'html', 19 | 'images', 20 | 'css', 21 | 'js', 22 | 'synchronized', 23 | 'created', 24 | ) 25 | 26 | def has_add_permission(self, request): 27 | return False 28 | 29 | def fetch_pages(modeladmin, request, queryset): 30 | if api.api_getpageslist(): 31 | messages.add_message( 32 | request, 33 | messages.SUCCESS, 34 | _(u'Pages successfuly fetched from Tilda') 35 | ) 36 | else: 37 | messages.add_message( 38 | request, 39 | messages.ERROR, 40 | _(u'Nothing fetched. Perharps wrong settings') 41 | ) 42 | fetch_pages.label = _(u'Fetch pages') 43 | 44 | def synchronize_page(self, request, obj): 45 | if api.api_getpageexport(obj.id): 46 | messages.add_message( 47 | request, 48 | messages.SUCCESS, 49 | _(u'Page «{}» successfuly synced from Tilda'.format(obj.title)) 50 | ) 51 | else: 52 | messages.add_message( 53 | request, 54 | messages.ERROR, 55 | _(u'Something wrong...') 56 | ) 57 | synchronize_page.label = _(u'Synchronize') 58 | 59 | change_actions = ('synchronize_page', ) 60 | changelist_actions = ('fetch_pages', ) 61 | 62 | admin.site.register(models.TildaPage, TildaPageAdmin) 63 | -------------------------------------------------------------------------------- /tilda/api.py: -------------------------------------------------------------------------------- 1 | import os 2 | import requests 3 | from django.conf import settings 4 | from django.utils.timezone import now 5 | 6 | from .helpers import download_file, make_unique 7 | from . import models 8 | 9 | 10 | API_HOST = 'http://api.tildacdn.info/v1' 11 | API_PAYLOAD = { 12 | 'publickey': settings.TILDA_PUBLIC_KEY, 13 | 'secretkey': settings.TILDA_SECRET_KEY 14 | } 15 | 16 | 17 | def api_getpageslist(): 18 | url = '{}/getpageslist'.format(API_HOST) 19 | payload = API_PAYLOAD.copy() 20 | payload['projectid'] = settings.TILDA_PROJECTID 21 | req = requests.get(url, params=payload, verify=False) 22 | if req.status_code == 200: 23 | res = req.json() 24 | if res['status'] == 'FOUND': 25 | for r in res['result']: 26 | obj, created = models.TildaPage.objects.get_or_create( 27 | id=r['id'] 28 | ) 29 | 30 | obj.title = r['title'] 31 | obj.save() 32 | return True 33 | return False 34 | 35 | 36 | def api_getpageexport(page_id): 37 | url = '{}/getpageexport'.format(API_HOST) 38 | page = models.TildaPage.objects.get(id=page_id) 39 | payload = API_PAYLOAD.copy() 40 | payload['pageid'] = page.id 41 | req = requests.get(url, params=payload, verify=False) 42 | if req.status_code == 200: 43 | res = req.json() 44 | if res['status'] == 'FOUND': 45 | 46 | """ 47 | Remove old images 48 | """ 49 | 50 | for img in page._path_images_list(): 51 | if os.path.exists(img): 52 | os.remove(img) 53 | 54 | """ 55 | Download new images, css, js 56 | """ 57 | 58 | result = res['result'] 59 | page.title = result['title'] 60 | page.html = result['html'] 61 | page.images = result['images'] 62 | page.css = result['css'] 63 | page.js = result['js'] 64 | page.synchronized = now() 65 | page.save() 66 | 67 | for r in make_unique(result['images']): 68 | filename = os.path.join(settings.TILDA_MEDIA_IMAGES, r['to']) 69 | download_file(r['from'], filename) 70 | url = os.path.join(settings.TILDA_MEDIA_IMAGES_URL, r['to']) 71 | page.html = page.html.replace(r['to'], url) 72 | page.save() 73 | 74 | for r in make_unique(result['css']): 75 | filename = os.path.join(settings.TILDA_MEDIA_CSS, r['to']) 76 | download_file(r['from'], filename) 77 | 78 | for r in make_unique(result['js']): 79 | filename = os.path.join(settings.TILDA_MEDIA_JS, r['to']) 80 | download_file(r['from'], filename) 81 | 82 | return True 83 | return False 84 | -------------------------------------------------------------------------------- /tilda/fields.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.conf import settings 3 | from django.db import models 4 | from django.template.loader import render_to_string 5 | from django.apps import apps 6 | 7 | 8 | class TildaWidget(forms.Widget): 9 | 10 | def render(self, name, value, attrs=None): 11 | is_required = self.is_required 12 | 13 | if not hasattr(settings, 'TILDA_PUBLIC_KEY') or \ 14 | not hasattr(settings, 'TILDA_SECRET_KEY') or \ 15 | not hasattr(settings, 'TILDA_PROJECTID') or \ 16 | not settings.TILDA_PUBLIC_KEY or \ 17 | not settings.TILDA_SECRET_KEY or \ 18 | not settings.TILDA_PROJECTID: 19 | is_need_config = True 20 | 21 | TildaPage = apps.get_model('tilda', 'TildaPage') 22 | queryset = TildaPage.objects.all() 23 | 24 | if queryset and value: 25 | obj = queryset.filter(id=value)[0] 26 | return render_to_string('tilda/widget.html', locals()) 27 | 28 | 29 | class TildaPageField(models.ForeignKey): 30 | 31 | def __init__(self, *args, **kwargs): 32 | kwargs['to'] = 'tilda.TildaPage' 33 | super(TildaPageField, self).__init__(*args, **kwargs) 34 | 35 | def formfield(self, **kwargs): 36 | kwargs['widget'] = TildaWidget 37 | return super(TildaPageField, self).formfield(**kwargs) 38 | -------------------------------------------------------------------------------- /tilda/helpers.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | def download_file(url, filename): 5 | headers = {'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:98.0) Gecko/20100101 Firefox/98.0'} 6 | r = requests.get(url, stream=True, verify=False, headers=headers) 7 | with open(filename, 'wb') as fd: 8 | for chunk in r.iter_content(chunk_size=128): 9 | fd.write(chunk) 10 | 11 | 12 | def make_unique(original_list): 13 | unique_list = [] 14 | [unique_list.append(obj) for obj in original_list if obj not in unique_list] 15 | return unique_list 16 | -------------------------------------------------------------------------------- /tilda/locale/ru/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1vank1n/django-tilda/3e4eaf0b8e63e6cbd717ea11d69b0d3025c15c9f/tilda/locale/ru/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /tilda/locale/ru/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 , 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: 2017-12-25 23:35+0300\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \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=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" 20 | "%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n" 21 | "%100>=11 && n%100<=14)? 2 : 3);\n" 22 | 23 | #: admin.py:34 24 | msgid "Pages successfuly fetched from Tilda" 25 | msgstr "Страницы успешно подгружены с Tila" 26 | 27 | #: admin.py:40 28 | msgid "Nothing fetched. Perharps wrong settings" 29 | msgstr "Ничего не загрузилось. Проверьте настройки в settings" 30 | 31 | #: admin.py:42 32 | msgid "Fetch pages" 33 | msgstr "Загрузить страницы" 34 | 35 | #: admin.py:49 36 | msgid "Page «{}» successfuly synced from Tilda" 37 | msgstr "Страница «{}» успешно загружена с Tilda" 38 | 39 | #: admin.py:55 40 | msgid "Something wrong..." 41 | msgstr "Что-то пошло не так..." 42 | 43 | #: admin.py:57 44 | msgid "Synchronize" 45 | msgstr "Синхронизировать" 46 | 47 | #: models.py:11 48 | msgid "Page id" 49 | msgstr "ID страницы" 50 | 51 | #: models.py:18 52 | msgid "Title" 53 | msgstr "Заголовок" 54 | 55 | #: models.py:23 56 | msgid "HTML" 57 | msgstr "" 58 | 59 | #: models.py:28 60 | msgid "Images" 61 | msgstr "Изображения" 62 | 63 | #: models.py:33 64 | msgid "CSS" 65 | msgstr "" 66 | 67 | #: models.py:38 68 | msgid "JS" 69 | msgstr "" 70 | 71 | #: models.py:43 72 | msgid "Synchronized time" 73 | msgstr "Время синхронизиации" 74 | 75 | #: models.py:49 76 | msgid "Created" 77 | msgstr "Дата создания" 78 | 79 | #: models.py:55 80 | msgid "page" 81 | msgstr "страница" 82 | 83 | #: models.py:56 84 | msgid "Tilda Pages" 85 | msgstr "Tilda Страницы" 86 | 87 | #: templates/tilda/widget.html:6 88 | msgid "" 89 | "Insert TILDA_PUBLIC_KEY, TILDA_SECRET_KEY, TILDA_PROJECTID in settings.py" 90 | msgstr "" 91 | "Добавьте TILDA_PUBLIC_KEY, TILDA_SECRET_KEY, TILDA_PROJECTID в settings.py" 92 | 93 | #: templates/tilda/widget.html:17 94 | msgid "Fetch list of pages from tilda.cc" 95 | msgstr "Подгрузить список страниц с tilda.cc" 96 | 97 | #: templates/tilda/widget.html:22 98 | msgid "Last sync" 99 | msgstr "Последняя синхронизация" 100 | 101 | #: templates/tilda/widget.html:22 102 | msgid "Sync" 103 | msgstr "Синхронизировать" 104 | 105 | #: templates/tilda/widget.html:44 106 | msgid "Syncing. Wait..." 107 | msgstr "Синхронизируем. Подождите..." 108 | 109 | #: templates/tilda/widget.html:53 templates/tilda/widget.html:58 110 | msgid "Error: Sync from tilda.cc give error." 111 | msgstr "Ошибка: Синхронизация с tilda.cc выдала ошибку" 112 | 113 | #: templates/tilda/widget.html:67 114 | msgid "Downloading. Wait..." 115 | msgstr "Загружаем. Подождите..." 116 | 117 | #: templates/tilda/widget.html:76 templates/tilda/widget.html:81 118 | msgid "Error: Fetch pages from tilda.cc give error." 119 | msgstr "Ошибка: Загрузка списка страниц с tilda.cc выдало ошибку." 120 | -------------------------------------------------------------------------------- /tilda/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.8 on 2017-12-25 14:40 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='TildaPage', 18 | fields=[ 19 | ('id', models.CharField(max_length=50, primary_key=True, serialize=False, unique=True, verbose_name='Page id')), 20 | ('title', models.CharField(max_length=100, verbose_name='Title')), 21 | ('html', models.TextField(blank=True, verbose_name='HTML')), 22 | ('images', models.TextField(blank=True, verbose_name='Images')), 23 | ('css', models.TextField(blank=True, verbose_name='CSS')), 24 | ('js', models.TextField(blank=True, verbose_name='JS')), 25 | ('synchronized', models.DateTimeField(blank=True, null=True, verbose_name='Synchronized time')), 26 | ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), 27 | ], 28 | options={ 29 | 'verbose_name': 'page', 30 | 'verbose_name_plural': 'Tilda Pages', 31 | 'ordering': ('title',), 32 | }, 33 | ), 34 | ] 35 | -------------------------------------------------------------------------------- /tilda/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1vank1n/django-tilda/3e4eaf0b8e63e6cbd717ea11d69b0d3025c15c9f/tilda/migrations/__init__.py -------------------------------------------------------------------------------- /tilda/models.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import os 3 | from django.db import models 4 | from django.conf import settings 5 | from django.utils.translation import gettext_lazy as _ 6 | 7 | 8 | class TildaPage(models.Model): 9 | 10 | id = models.CharField( 11 | _(u'Page id'), 12 | max_length=50, 13 | primary_key=True, 14 | unique=True 15 | ) 16 | 17 | title = models.CharField( 18 | _(u'Title'), 19 | max_length=100 20 | ) 21 | 22 | html = models.TextField( 23 | _(u'HTML'), 24 | blank=True 25 | ) 26 | 27 | images = models.TextField( 28 | _(u'Images'), 29 | blank=True 30 | ) 31 | 32 | css = models.TextField( 33 | _(u'CSS'), 34 | blank=True 35 | ) 36 | 37 | js = models.TextField( 38 | _(u'JS'), 39 | blank=True 40 | ) 41 | 42 | synchronized = models.DateTimeField( 43 | _(u'Synchronized time'), 44 | blank=True, 45 | null=True 46 | ) 47 | 48 | created = models.DateTimeField( 49 | _(u'Created'), 50 | auto_now_add=True 51 | ) 52 | 53 | class Meta: 54 | ordering = ('title', ) 55 | verbose_name = _(u'page') 56 | verbose_name_plural = _(u'Tilda Pages') 57 | 58 | def get_images_list(self): 59 | if self.images: 60 | return [ 61 | os.path.join('/media/tilda/images', r['to']) 62 | for r in eval(self.images) 63 | ] 64 | return [] 65 | 66 | def get_css_list(self): 67 | if self.css: 68 | return [ 69 | os.path.join('/media/tilda/css', r['to']) 70 | for r in eval(self.css) 71 | ] 72 | return [] 73 | 74 | def get_js_list(self): 75 | if self.js: 76 | return [ 77 | os.path.join('/media/tilda/js', r['to']) 78 | for r in eval(self.js) 79 | ] 80 | return [] 81 | 82 | def _path_images_list(self): 83 | if self.images: 84 | return [ 85 | os.path.join(settings.TILDA_MEDIA_IMAGES, r['to']) 86 | for r in eval(self.images) 87 | ] 88 | return [] 89 | 90 | def _path_css_list(self): 91 | if self.css: 92 | return [ 93 | os.path.join(settings.TILDA_MEDIA_CSS, r['to']) 94 | for r in eval(self.css) 95 | ] 96 | return [] 97 | 98 | def _path_js_list(self): 99 | if self.js: 100 | return [ 101 | os.path.join(settings.TILDA_MEDIA_JS, r['to']) 102 | for r in eval(self.js) 103 | ] 104 | return [] 105 | 106 | def __unicode__(self): 107 | return self.title 108 | 109 | def __str__(self): 110 | return self.title 111 | -------------------------------------------------------------------------------- /tilda/templates/tilda/widget.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | 3 | 4 | {% if is_need_config %} 5 |
6 | {% trans "Insert TILDA_PUBLIC_KEY, TILDA_SECRET_KEY, TILDA_PROJECTID in settings.py" %} 7 |
8 | {% else %} 9 |
10 | 16 | 17 | {% trans "Fetch list of pages from tilda.cc" %} 18 |
19 | 20 | {% if value %} 21 |
22 | {% trans "Last sync" %}: {% if obj.synchronized %}{{ obj.synchronized|date:"d.m.Y H:i:s" }}{% else %}---{% endif %}. {% trans "Sync" %} 23 |
24 | {% endif %} 25 | {% endif %} 26 | 27 | 28 | 88 | --------------------------------------------------------------------------------