├── .gitignore ├── .landscape.yaml ├── AUTHORS.rst ├── LICENSE ├── MANIFEST.in ├── README.rst ├── example ├── README.md ├── __init__.py ├── manage.py ├── requirements.txt ├── settings.py ├── templates │ └── flatpages │ │ └── default.html └── urls.py ├── logo ├── logo.png ├── sizes 1024x615 │ ├── black.png │ ├── black.svg │ ├── color.png │ ├── color.svg │ ├── white.png │ └── white.svg ├── sizes 256x154 │ ├── black.png │ ├── black.svg │ ├── color.png │ ├── color.svg │ ├── white.png │ └── white.svg ├── sizes 500x300 │ ├── black.png │ ├── black.svg │ ├── color.png │ ├── color.svg │ ├── white.png │ └── white.svg ├── sizes 60x36 │ ├── black.png │ ├── black.svg │ ├── color.png │ ├── color.svg │ ├── white.png │ └── white.svg ├── sizes 72x43 │ ├── black.png │ ├── black.svg │ ├── color.png │ ├── color.svg │ ├── white.png │ └── white.svg └── sizes 96x58 │ ├── black.png │ ├── black.svg │ ├── color.png │ ├── color.svg │ ├── white.png │ └── white.svg ├── seo ├── __init__.py ├── admin.py ├── forms.py ├── importpath.py ├── locale │ └── ru │ │ └── LC_MESSAGES │ │ ├── django.mo │ │ └── django.po ├── managers.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── south_migrations │ ├── 0001_initial.py │ ├── 0002_auto__add_unique_seo_object_id_content_type.py │ ├── 0003_auto__add_field_seo_site__add_field_url_site__del_unique_url_url__add_.py │ ├── 0004_auto__del_url__del_unique_url_url_site.py │ ├── 0005_auto__del_field_seo_site.py │ ├── 0006_auto__add_url__add_unique_url_site_url.py │ ├── 0007_auto__del_unique_url_url.py │ └── __init__.py └── templatetags │ ├── __init__.py │ └── seo_tags.py ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | -------------------------------------------------------------------------------- /.landscape.yaml: -------------------------------------------------------------------------------- 1 | ignore-paths: 2 | - south_migrations 3 | - migrations 4 | - example 5 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Credits 3 | ======= 4 | 5 | Maintainer 6 | ---------- 7 | 8 | * Basil Shubin - 9 | 10 | Contributors 11 | ------------ 12 | 13 | None yet. Why not be the first? 14 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.rst 3 | recursive-include seo/locale * 4 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | django-easy-seo 2 | =============== 3 | 4 | SEO fields for objects of any model registered in admin. 5 | 6 | Maintained by `Basil Shubin `_, and some great 7 | `contributors `_. 8 | 9 | .. image:: https://img.shields.io/pypi/v/django-easy-seo.svg 10 | :target: https://pypi.python.org/pypi/django-easy-seo/ 11 | 12 | .. image:: https://img.shields.io/pypi/dm/django-easy-seo.svg 13 | :target: https://pypi.python.org/pypi/django-easy-seo/ 14 | 15 | .. image:: https://img.shields.io/github/license/bashu/django-easy-seo.svg 16 | :target: https://pypi.python.org/pypi/django-easy-seo/ 17 | 18 | .. raw:: html 19 | 20 |

21 | django-easy-seo 22 |

23 | 24 | Setup 25 | ----- 26 | 27 | Either clone this repository into your project, or install with ``pip install django-easy-seo`` 28 | 29 | You'll need to add ``seo`` as a **LAST** item to ``INSTALLED_APPS`` in your project's ``settings.py`` file : 30 | 31 | .. code-block:: python 32 | 33 | INSTALLED_APPS = ( 34 | ... 35 | 'seo', # must be last in a list 36 | ) 37 | 38 | Then run ``./manage.py syncdb`` to create the required database tables 39 | 40 | Configuration 41 | ------------- 42 | 43 | There is only one mandatory configuration option you need to set in your ``settings.py`` : 44 | 45 | .. code-block:: python 46 | 47 | # Override / extend ModelAdmin classes for a given Models 48 | SEO_FOR_MODELS = [ 49 | '.models.', 50 | ] 51 | 52 | Usage 53 | ----- 54 | 55 | First of all, load the ``seo_tags`` in every template where you want to use it : 56 | 57 | .. code-block:: html+django 58 | 59 | {% load seo_tags %} 60 | 61 | Use : 62 | 63 | .. code-block:: html+django 64 | 65 | {% seo '' for object %} 66 | 67 | or : 68 | 69 | .. code-block:: html+django 70 | 71 | {% seo '' for object as variable %} 72 | {{ variable }} 73 | 74 | Please see ``example`` application. This application is used to manually test the functionalities of this package. This also serves as a good example. 75 | 76 | You need only Django 1.4 or above to run that. It might run on older versions but that is not tested. 77 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Example 2 | 3 | To run the example application, make sure you have the required 4 | packages installed. You can do this using following commands : 5 | 6 | ```shell 7 | mkvirtualenv example 8 | pip install -r example/requirements.txt 9 | ``` 10 | 11 | This assumes you already have ``virtualenv`` and ``virtualenvwrapper`` 12 | installed and configured. 13 | 14 | Next, you can setup the django instance using : 15 | 16 | ```shell 17 | python example/manage.py syncdb --noinput 18 | python example/manage.py createsuperuser --username=admin --email=admin@example.com 19 | ``` 20 | 21 | And run it : 22 | ```shell 23 | python example/manage.py runserver 24 | ``` 25 | 26 | Good luck! 27 | -------------------------------------------------------------------------------- /example/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bashu/django-easy-seo/aa0f457e3ee78c51857b777fbb270a5bb25f62ee/example/__init__.py -------------------------------------------------------------------------------- /example/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", "example.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | # Allow starting the app without installing the module. 11 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) 12 | 13 | execute_from_command_line(sys.argv) 14 | -------------------------------------------------------------------------------- /example/requirements.txt: -------------------------------------------------------------------------------- 1 | django 2 | django-classy-tags 3 | -------------------------------------------------------------------------------- /example/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for example project. 3 | 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/1.7/topics/settings/ 6 | 7 | For the full list of settings and their values, see 8 | https://docs.djangoproject.com/en/1.7/ref/settings/ 9 | """ 10 | 11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 12 | import os 13 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 14 | 15 | # Quick-start development settings - unsuitable for production 16 | # See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/ 17 | 18 | # SECURITY WARNING: keep the secret key used in production secret! 19 | SECRET_KEY = 'YOUR_SECRET_KEY' 20 | 21 | # SECURITY WARNING: don't run with debug turned on in production! 22 | DEBUG = True 23 | 24 | TEMPLATE_DEBUG = True 25 | 26 | ALLOWED_HOSTS = [] 27 | 28 | 29 | # Application definition 30 | 31 | PROJECT_APPS = [ 32 | 'seo', 33 | ] 34 | 35 | INSTALLED_APPS = [ 36 | 'django.contrib.auth', 37 | 'django.contrib.sites', 38 | 'django.contrib.admin', 39 | 'django.contrib.sessions', 40 | 'django.contrib.messages', 41 | 'django.contrib.staticfiles', 42 | 'django.contrib.contenttypes', 43 | 'django.contrib.flatpages', 44 | ] + PROJECT_APPS 45 | 46 | MIDDLEWARE = ( 47 | 'django.contrib.sessions.middleware.SessionMiddleware', 48 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 | 'django.contrib.messages.middleware.MessageMiddleware', 50 | 'django.middleware.common.CommonMiddleware', 51 | 'django.middleware.csrf.CsrfViewMiddleware', 52 | 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware', 53 | ) 54 | 55 | ROOT_URLCONF = 'example.urls' 56 | 57 | TEMPLATES = [ 58 | { 59 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 60 | 'APP_DIRS': True, 61 | 'DIRS': [ 62 | os.path.join(os.path.dirname(__file__), 'templates'), 63 | ], 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 | SITE_ID = 1 76 | 77 | 78 | # Database 79 | # https://docs.djangoproject.com/en/1.7/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 | # Internationalization 89 | # https://docs.djangoproject.com/en/1.7/topics/i18n/ 90 | 91 | LANGUAGE_CODE = 'en-us' 92 | 93 | TIME_ZONE = 'UTC' 94 | 95 | USE_I18N = True 96 | 97 | USE_L10N = True 98 | 99 | USE_TZ = True 100 | 101 | 102 | # Static files (CSS, JavaScript, Images) 103 | # https://docs.djangoproject.com/en/1.7/howto/static-files/ 104 | 105 | STATIC_URL = '/static/' 106 | 107 | 108 | ## SEO settings 109 | 110 | SEO_FOR_MODELS = [ 111 | 'django.contrib.flatpages.models.FlatPage', 112 | ] 113 | 114 | -------------------------------------------------------------------------------- /example/templates/flatpages/default.html: -------------------------------------------------------------------------------- 1 | {% load seo_tags %} 2 | 3 | 4 | 5 | 6 | {% seo 'title' for flatpage %} 7 | 8 | 9 | {% seo 'title' for flatpage as title %} 10 |

{{ title }}

11 | {{ flatpage.content }} 12 | 13 | 14 | -------------------------------------------------------------------------------- /example/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.urls import path 4 | from django.conf.urls import * 5 | 6 | from django.contrib import admin 7 | admin.autodiscover() 8 | 9 | urlpatterns = [ 10 | path('admin/', admin.site.urls), 11 | ] 12 | -------------------------------------------------------------------------------- /logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bashu/django-easy-seo/aa0f457e3ee78c51857b777fbb270a5bb25f62ee/logo/logo.png -------------------------------------------------------------------------------- /logo/sizes 1024x615/black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bashu/django-easy-seo/aa0f457e3ee78c51857b777fbb270a5bb25f62ee/logo/sizes 1024x615/black.png -------------------------------------------------------------------------------- /logo/sizes 1024x615/black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /logo/sizes 1024x615/color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bashu/django-easy-seo/aa0f457e3ee78c51857b777fbb270a5bb25f62ee/logo/sizes 1024x615/color.png -------------------------------------------------------------------------------- /logo/sizes 1024x615/color.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /logo/sizes 1024x615/white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bashu/django-easy-seo/aa0f457e3ee78c51857b777fbb270a5bb25f62ee/logo/sizes 1024x615/white.png -------------------------------------------------------------------------------- /logo/sizes 1024x615/white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /logo/sizes 256x154/black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bashu/django-easy-seo/aa0f457e3ee78c51857b777fbb270a5bb25f62ee/logo/sizes 256x154/black.png -------------------------------------------------------------------------------- /logo/sizes 256x154/black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /logo/sizes 256x154/color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bashu/django-easy-seo/aa0f457e3ee78c51857b777fbb270a5bb25f62ee/logo/sizes 256x154/color.png -------------------------------------------------------------------------------- /logo/sizes 256x154/color.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /logo/sizes 256x154/white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bashu/django-easy-seo/aa0f457e3ee78c51857b777fbb270a5bb25f62ee/logo/sizes 256x154/white.png -------------------------------------------------------------------------------- /logo/sizes 256x154/white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /logo/sizes 500x300/black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bashu/django-easy-seo/aa0f457e3ee78c51857b777fbb270a5bb25f62ee/logo/sizes 500x300/black.png -------------------------------------------------------------------------------- /logo/sizes 500x300/black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /logo/sizes 500x300/color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bashu/django-easy-seo/aa0f457e3ee78c51857b777fbb270a5bb25f62ee/logo/sizes 500x300/color.png -------------------------------------------------------------------------------- /logo/sizes 500x300/color.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /logo/sizes 500x300/white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bashu/django-easy-seo/aa0f457e3ee78c51857b777fbb270a5bb25f62ee/logo/sizes 500x300/white.png -------------------------------------------------------------------------------- /logo/sizes 500x300/white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /logo/sizes 60x36/black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bashu/django-easy-seo/aa0f457e3ee78c51857b777fbb270a5bb25f62ee/logo/sizes 60x36/black.png -------------------------------------------------------------------------------- /logo/sizes 60x36/black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /logo/sizes 60x36/color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bashu/django-easy-seo/aa0f457e3ee78c51857b777fbb270a5bb25f62ee/logo/sizes 60x36/color.png -------------------------------------------------------------------------------- /logo/sizes 60x36/color.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /logo/sizes 60x36/white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bashu/django-easy-seo/aa0f457e3ee78c51857b777fbb270a5bb25f62ee/logo/sizes 60x36/white.png -------------------------------------------------------------------------------- /logo/sizes 60x36/white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /logo/sizes 72x43/black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bashu/django-easy-seo/aa0f457e3ee78c51857b777fbb270a5bb25f62ee/logo/sizes 72x43/black.png -------------------------------------------------------------------------------- /logo/sizes 72x43/black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /logo/sizes 72x43/color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bashu/django-easy-seo/aa0f457e3ee78c51857b777fbb270a5bb25f62ee/logo/sizes 72x43/color.png -------------------------------------------------------------------------------- /logo/sizes 72x43/color.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /logo/sizes 72x43/white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bashu/django-easy-seo/aa0f457e3ee78c51857b777fbb270a5bb25f62ee/logo/sizes 72x43/white.png -------------------------------------------------------------------------------- /logo/sizes 72x43/white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /logo/sizes 96x58/black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bashu/django-easy-seo/aa0f457e3ee78c51857b777fbb270a5bb25f62ee/logo/sizes 96x58/black.png -------------------------------------------------------------------------------- /logo/sizes 96x58/black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /logo/sizes 96x58/color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bashu/django-easy-seo/aa0f457e3ee78c51857b777fbb270a5bb25f62ee/logo/sizes 96x58/color.png -------------------------------------------------------------------------------- /logo/sizes 96x58/color.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /logo/sizes 96x58/white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bashu/django-easy-seo/aa0f457e3ee78c51857b777fbb270a5bb25f62ee/logo/sizes 96x58/white.png -------------------------------------------------------------------------------- /logo/sizes 96x58/white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /seo/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.4.8' 2 | -------------------------------------------------------------------------------- /seo/admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.conf import settings 4 | from django.contrib import admin 5 | from django.contrib.contenttypes.admin import GenericStackedInline 6 | from django.core.exceptions import ImproperlyConfigured 7 | 8 | from .models import Seo 9 | from .forms import SeoForm 10 | from .importpath import importpath 11 | 12 | 13 | class SeoAdmin(admin.ModelAdmin): 14 | list_display = ['__str__', 'content_object'] 15 | 16 | def queryset(self, request): 17 | return super(SeoAdmin, self).queryset(request).no_cache() 18 | 19 | try: 20 | admin.site.register(Seo, SeoAdmin) 21 | except admin.sites.AlreadyRegistered: 22 | pass 23 | 24 | 25 | class SeoInlines(GenericStackedInline): 26 | model = Seo 27 | form = SeoForm 28 | extra = 1 29 | max_num = 1 30 | 31 | def queryset(self, request): 32 | return super(SeoInlines, self).queryset(request).no_cache() 33 | 34 | 35 | for model_name in getattr(settings, 'SEO_FOR_MODELS', []): 36 | model = importpath(model_name, 'SEO_FOR_MODELS') 37 | try: 38 | model_admin = admin.site._registry[model].__class__ 39 | except KeyError: 40 | raise ImproperlyConfigured( 41 | "Please put ``seo`` in your settings.py only as last INSTALLED_APPS") 42 | admin.site.unregister(model) 43 | 44 | setattr(model_admin, 'inlines', getattr(model_admin, 'inlines', [])) 45 | if not SeoInlines in model_admin.inlines: 46 | model_admin.inlines = list(model_admin.inlines)[:] + [SeoInlines] 47 | 48 | admin.site.register(model, model_admin) 49 | -------------------------------------------------------------------------------- /seo/forms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django import forms 4 | 5 | from .models import Seo 6 | 7 | 8 | class SeoForm(forms.ModelForm): 9 | 10 | class Meta: 11 | model = Seo 12 | fields = ['title', 'description', 'keywords'] 13 | widgets = { 14 | 'title': forms.Textarea(attrs={'cols': 120, 'rows': 2}), 15 | 'description': forms.Textarea(attrs={'cols': 120, 'rows': 2}), 16 | 'keywords': forms.Textarea(attrs={'cols': 120, 'rows': 5}), 17 | } 18 | 19 | -------------------------------------------------------------------------------- /seo/importpath.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.core.exceptions import ImproperlyConfigured 4 | 5 | 6 | def importpath(path, error_text=None): 7 | """ 8 | Import value by specified ``path``. 9 | Value can represent module, class, object, attribute or method. 10 | If ``error_text`` is not None and import will 11 | raise ImproperlyConfigured with user friendly text. 12 | 13 | """ 14 | result = None 15 | attrs = [] 16 | parts = path.split('.') 17 | exception = None 18 | while parts: 19 | try: 20 | result = __import__('.'.join(parts), {}, {}, ['']) 21 | except ImportError as e: 22 | if exception is None: 23 | exception = e 24 | attrs = parts[-1:] + attrs 25 | parts = parts[:-1] 26 | else: 27 | break 28 | for attr in attrs: 29 | try: 30 | result = getattr(result, attr) 31 | except (AttributeError, ValueError) as e: 32 | if error_text is not None: 33 | raise ImproperlyConfigured('Error: %s can import "%s"' % ( 34 | error_text, path)) 35 | else: 36 | raise exception 37 | return result 38 | -------------------------------------------------------------------------------- /seo/locale/ru/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bashu/django-easy-seo/aa0f457e3ee78c51857b777fbb270a5bb25f62ee/seo/locale/ru/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /seo/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 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: \n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2013-11-15 09:50+0700\n" 11 | "PO-Revision-Date: 2013-11-15 09:52+0700\n" 12 | "Last-Translator: Basil Shubin \n" 13 | "Language-Team: \n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | "X-Generator: Poedit 1.5.7\n" 18 | 19 | #: models.py:53 20 | msgid "title" 21 | msgstr "заголовок" 22 | 23 | #: models.py:55 24 | msgid "description" 25 | msgstr "описание" 26 | 27 | #: models.py:57 28 | msgid "keywords" 29 | msgstr "ключевые слова" 30 | 31 | #: models.py:68 models.py:69 32 | msgid "SEO fields" 33 | msgstr "SEO поля" 34 | 35 | #: models.py:79 models.py:88 36 | msgid "URL" 37 | msgstr "УРЛ" 38 | 39 | #: models.py:80 40 | msgid "" 41 | "This should be an absolute path, excluding the domain name. Example: '/" 42 | "about/'" 43 | msgstr "" 44 | 45 | #: models.py:89 46 | msgid "URLs" 47 | msgstr "УРЛы" 48 | -------------------------------------------------------------------------------- /seo/managers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.db import models 4 | from django.core.exceptions import ObjectDoesNotExist 5 | from django.contrib.contenttypes.models import ContentType 6 | 7 | 8 | class SeoManager(models.Manager): 9 | 10 | def for_object(self, instance): 11 | ct = ContentType.objects.get_for_model(instance.__class__) 12 | 13 | try: 14 | return self.filter(content_type=ct).get(object_id=instance.id) 15 | except ObjectDoesNotExist: 16 | return None 17 | -------------------------------------------------------------------------------- /seo/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import seo.models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('contenttypes', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Seo', 17 | fields=[ 18 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 19 | ('title', models.CharField(default=b'', max_length=200, verbose_name='title', blank=True)), 20 | ('description', models.CharField(default=b'', max_length=200, verbose_name='description', blank=True)), 21 | ('keywords', models.CharField(default=b'', max_length=1000, verbose_name='keywords', blank=True)), 22 | ('object_id', models.PositiveIntegerField()), 23 | ('content_type', models.ForeignKey(to='contenttypes.ContentType', on_delete=models.CASCADE)), 24 | ], 25 | options={ 26 | 'verbose_name': 'SEO fields', 27 | 'verbose_name_plural': 'SEO fields', 28 | }, 29 | bases=(models.Model,), 30 | ), 31 | migrations.AlterUniqueTogether( 32 | name='seo', 33 | unique_together=set([('content_type', 'object_id')]), 34 | ), 35 | ] 36 | -------------------------------------------------------------------------------- /seo/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bashu/django-easy-seo/aa0f457e3ee78c51857b777fbb270a5bb25f62ee/seo/migrations/__init__.py -------------------------------------------------------------------------------- /seo/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.db import models 4 | from django.utils.translation import ugettext_lazy as _ 5 | from django.contrib.contenttypes.models import ContentType 6 | from django.contrib.contenttypes.fields import GenericForeignKey 7 | from django.utils.encoding import python_2_unicode_compatible 8 | 9 | from .managers import SeoManager 10 | 11 | 12 | @python_2_unicode_compatible 13 | class Seo(models.Model): 14 | 15 | title = models.CharField( 16 | verbose_name=_('title'), max_length=200, default='', blank=True) 17 | description = models.CharField( 18 | verbose_name=_('description'), max_length=200, default='', blank=True) 19 | keywords = models.CharField( 20 | verbose_name=_('keywords'), max_length=1000, default='', blank=True) 21 | 22 | content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) 23 | object_id = models.PositiveIntegerField() 24 | content_object = GenericForeignKey('content_type', 'object_id') 25 | 26 | objects = SeoManager() 27 | 28 | class Meta: 29 | verbose_name = _('SEO fields') 30 | verbose_name_plural = _('SEO fields') 31 | unique_together = ("content_type", "object_id") 32 | 33 | def __str__(self): 34 | return self.title 35 | -------------------------------------------------------------------------------- /seo/south_migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | class Migration(SchemaMigration): 8 | 9 | def forwards(self, orm): 10 | 11 | # Adding model 'Seo' 12 | db.create_table('seo_seo', ( 13 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 14 | ('title', self.gf('django.db.models.fields.CharField')(default='', max_length=200, blank=True)), 15 | ('description', self.gf('django.db.models.fields.CharField')(default='', max_length=200, blank=True)), 16 | ('keywords', self.gf('django.db.models.fields.CharField')(default='', max_length=1000, blank=True)), 17 | ('content_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['contenttypes.ContentType'])), 18 | ('object_id', self.gf('django.db.models.fields.PositiveIntegerField')()), 19 | )) 20 | db.send_create_signal('seo', ['Seo']) 21 | 22 | # Adding model 'Url' 23 | db.create_table('seo_url', ( 24 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 25 | ('url', self.gf('django.db.models.fields.CharField')(default='/', unique=True, max_length=200)), 26 | )) 27 | db.send_create_signal('seo', ['Url']) 28 | 29 | 30 | def backwards(self, orm): 31 | 32 | # Deleting model 'Seo' 33 | db.delete_table('seo_seo') 34 | 35 | # Deleting model 'Url' 36 | db.delete_table('seo_url') 37 | 38 | 39 | models = { 40 | 'contenttypes.contenttype': { 41 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 42 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 43 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 44 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 45 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 46 | }, 47 | 'seo.seo': { 48 | 'Meta': {'object_name': 'Seo'}, 49 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 50 | 'description': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '200', 'blank': 'True'}), 51 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 52 | 'keywords': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1000', 'blank': 'True'}), 53 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 54 | 'title': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '200', 'blank': 'True'}) 55 | }, 56 | 'seo.url': { 57 | 'Meta': {'object_name': 'Url'}, 58 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 59 | 'url': ('django.db.models.fields.CharField', [], {'default': "'/'", 'unique': 'True', 'max_length': '200'}) 60 | } 61 | } 62 | 63 | complete_apps = ['seo'] 64 | -------------------------------------------------------------------------------- /seo/south_migrations/0002_auto__add_unique_seo_object_id_content_type.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | class Migration(SchemaMigration): 8 | 9 | def forwards(self, orm): 10 | 11 | # Adding unique constraint on 'Seo', fields ['object_id', 'content_type'] 12 | db.create_unique('seo_seo', ['object_id', 'content_type_id']) 13 | 14 | 15 | def backwards(self, orm): 16 | 17 | # Removing unique constraint on 'Seo', fields ['object_id', 'content_type'] 18 | db.delete_unique('seo_seo', ['object_id', 'content_type_id']) 19 | 20 | 21 | models = { 22 | 'contenttypes.contenttype': { 23 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 24 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 25 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 26 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 27 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 28 | }, 29 | 'seo.seo': { 30 | 'Meta': {'unique_together': "(('content_type', 'object_id'),)", 'object_name': 'Seo'}, 31 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 32 | 'description': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '200', 'blank': 'True'}), 33 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 34 | 'keywords': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1000', 'blank': 'True'}), 35 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 36 | 'title': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '200', 'blank': 'True'}) 37 | }, 38 | 'seo.url': { 39 | 'Meta': {'object_name': 'Url'}, 40 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 41 | 'url': ('django.db.models.fields.CharField', [], {'default': "'/'", 'unique': 'True', 'max_length': '200'}) 42 | } 43 | } 44 | 45 | complete_apps = ['seo'] 46 | -------------------------------------------------------------------------------- /seo/south_migrations/0003_auto__add_field_seo_site__add_field_url_site__del_unique_url_url__add_.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Removing unique constraint on 'Url', fields ['url'] 12 | db.delete_unique('seo_url', ['url']) 13 | 14 | # Adding field 'Seo.site' 15 | db.add_column('seo_seo', 'site', 16 | self.gf('django.db.models.fields.related.ForeignKey')(to=orm['sites.Site'], null=True, blank=True), 17 | keep_default=False) 18 | 19 | # Adding field 'Url.site' 20 | db.add_column('seo_url', 'site', 21 | self.gf('django.db.models.fields.related.ForeignKey')(to=orm['sites.Site'], null=True, blank=True), 22 | keep_default=False) 23 | 24 | # Adding unique constraint on 'Url', fields ['url', 'site'] 25 | db.create_unique('seo_url', ['url', 'site_id']) 26 | 27 | 28 | def backwards(self, orm): 29 | # Removing unique constraint on 'Url', fields ['url', 'site'] 30 | db.delete_unique('seo_url', ['url', 'site_id']) 31 | 32 | # Deleting field 'Seo.site' 33 | db.delete_column('seo_seo', 'site_id') 34 | 35 | # Deleting field 'Url.site' 36 | db.delete_column('seo_url', 'site_id') 37 | 38 | # Adding unique constraint on 'Url', fields ['url'] 39 | db.create_unique('seo_url', ['url']) 40 | 41 | 42 | models = { 43 | 'contenttypes.contenttype': { 44 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 45 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 46 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 47 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 48 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 49 | }, 50 | 'seo.seo': { 51 | 'Meta': {'unique_together': "(('content_type', 'object_id'),)", 'object_name': 'Seo'}, 52 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 53 | 'description': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '200', 'blank': 'True'}), 54 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 55 | 'keywords': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1000', 'blank': 'True'}), 56 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 57 | 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']", 'null': 'True', 'blank': 'True'}), 58 | 'title': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '200', 'blank': 'True'}) 59 | }, 60 | 'seo.url': { 61 | 'Meta': {'unique_together': "(('url', 'site'),)", 'object_name': 'Url'}, 62 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 63 | 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']", 'null': 'True', 'blank': 'True'}), 64 | 'url': ('django.db.models.fields.CharField', [], {'default': "'/'", 'max_length': '200'}) 65 | }, 66 | 'sites.site': { 67 | 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, 68 | 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 69 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 70 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 71 | } 72 | } 73 | 74 | complete_apps = ['seo'] -------------------------------------------------------------------------------- /seo/south_migrations/0004_auto__del_url__del_unique_url_url_site.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Removing unique constraint on 'Url', fields ['url', 'site'] 12 | db.delete_unique('seo_url', ['url', 'site_id']) 13 | 14 | # Deleting model 'Url' 15 | db.delete_table('seo_url') 16 | 17 | 18 | def backwards(self, orm): 19 | # Adding model 'Url' 20 | db.create_table('seo_url', ( 21 | ('url', self.gf('django.db.models.fields.CharField')(default='/', max_length=200)), 22 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 23 | ('site', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['sites.Site'], null=True, blank=True)), 24 | )) 25 | db.send_create_signal('seo', ['Url']) 26 | 27 | # Adding unique constraint on 'Url', fields ['url', 'site'] 28 | db.create_unique('seo_url', ['url', 'site_id']) 29 | 30 | 31 | models = { 32 | 'contenttypes.contenttype': { 33 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 34 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 35 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 36 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 37 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 38 | }, 39 | 'seo.seo': { 40 | 'Meta': {'unique_together': "(('content_type', 'object_id'),)", 'object_name': 'Seo'}, 41 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 42 | 'description': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '200', 'blank': 'True'}), 43 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 44 | 'keywords': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1000', 'blank': 'True'}), 45 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 46 | 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']", 'null': 'True', 'blank': 'True'}), 47 | 'title': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '200', 'blank': 'True'}) 48 | }, 49 | 'sites.site': { 50 | 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, 51 | 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 52 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 53 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 54 | } 55 | } 56 | 57 | complete_apps = ['seo'] -------------------------------------------------------------------------------- /seo/south_migrations/0005_auto__del_field_seo_site.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Deleting field 'Seo.site' 12 | db.delete_column('seo_seo', 'site_id') 13 | 14 | 15 | def backwards(self, orm): 16 | # Adding field 'Seo.site' 17 | db.add_column('seo_seo', 'site', 18 | self.gf('django.db.models.fields.related.ForeignKey')(to=orm['sites.Site'], null=True, blank=True), 19 | keep_default=False) 20 | 21 | 22 | models = { 23 | 'contenttypes.contenttype': { 24 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 25 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 26 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 27 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 28 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 29 | }, 30 | 'seo.seo': { 31 | 'Meta': {'unique_together': "(('content_type', 'object_id'),)", 'object_name': 'Seo'}, 32 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 33 | 'description': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '200', 'blank': 'True'}), 34 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 35 | 'keywords': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1000', 'blank': 'True'}), 36 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 37 | 'title': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '200', 'blank': 'True'}) 38 | } 39 | } 40 | 41 | complete_apps = ['seo'] -------------------------------------------------------------------------------- /seo/south_migrations/0006_auto__add_url__add_unique_url_site_url.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding model 'URL' 12 | db.create_table('seo_url', ( 13 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 14 | ('url', self.gf('django.db.models.fields.CharField')(default='/', unique=True, max_length=200)), 15 | ('site', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['sites.Site'], null=True, blank=True)), 16 | )) 17 | db.send_create_signal('seo', ['URL']) 18 | 19 | # Adding unique constraint on 'URL', fields ['site', 'url'] 20 | db.create_unique('seo_url', ['site_id', 'url']) 21 | 22 | 23 | def backwards(self, orm): 24 | # Removing unique constraint on 'URL', fields ['site', 'url'] 25 | db.delete_unique('seo_url', ['site_id', 'url']) 26 | 27 | # Deleting model 'URL' 28 | db.delete_table('seo_url') 29 | 30 | 31 | models = { 32 | 'contenttypes.contenttype': { 33 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 34 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 35 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 36 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 37 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 38 | }, 39 | 'seo.seo': { 40 | 'Meta': {'unique_together': "(('content_type', 'object_id'),)", 'object_name': 'Seo'}, 41 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 42 | 'description': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '200', 'blank': 'True'}), 43 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 44 | 'keywords': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1000', 'blank': 'True'}), 45 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 46 | 'title': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '200', 'blank': 'True'}) 47 | }, 48 | 'seo.url': { 49 | 'Meta': {'unique_together': "(('site', 'url'),)", 'object_name': 'URL'}, 50 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 51 | 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']", 'null': 'True', 'blank': 'True'}), 52 | 'url': ('django.db.models.fields.CharField', [], {'default': "'/'", 'unique': 'True', 'max_length': '200'}) 53 | }, 54 | 'sites.site': { 55 | 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, 56 | 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 57 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 58 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 59 | } 60 | } 61 | 62 | complete_apps = ['seo'] -------------------------------------------------------------------------------- /seo/south_migrations/0007_auto__del_unique_url_url.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Removing unique constraint on 'URL', fields ['url'] 12 | db.delete_unique('seo_url', ['url']) 13 | 14 | 15 | def backwards(self, orm): 16 | # Adding unique constraint on 'URL', fields ['url'] 17 | db.create_unique('seo_url', ['url']) 18 | 19 | 20 | models = { 21 | 'contenttypes.contenttype': { 22 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 23 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 24 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 25 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 26 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 27 | }, 28 | 'seo.seo': { 29 | 'Meta': {'unique_together': "(('content_type', 'object_id'),)", 'object_name': 'Seo'}, 30 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 31 | 'description': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '200', 'blank': 'True'}), 32 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 33 | 'keywords': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1000', 'blank': 'True'}), 34 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 35 | 'title': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '200', 'blank': 'True'}) 36 | }, 37 | 'seo.url': { 38 | 'Meta': {'unique_together': "(('site', 'url'),)", 'object_name': 'URL'}, 39 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 40 | 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']", 'null': 'True', 'blank': 'True'}), 41 | 'url': ('django.db.models.fields.CharField', [], {'default': "'/'", 'max_length': '200'}) 42 | }, 43 | 'sites.site': { 44 | 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, 45 | 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 46 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 47 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 48 | } 49 | } 50 | 51 | complete_apps = ['seo'] -------------------------------------------------------------------------------- /seo/south_migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bashu/django-easy-seo/aa0f457e3ee78c51857b777fbb270a5bb25f62ee/seo/south_migrations/__init__.py -------------------------------------------------------------------------------- /seo/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bashu/django-easy-seo/aa0f457e3ee78c51857b777fbb270a5bb25f62ee/seo/templatetags/__init__.py -------------------------------------------------------------------------------- /seo/templatetags/seo_tags.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django import template 4 | from django.utils.html import escape 5 | from django.db.models.base import Model 6 | 7 | from classytags.core import Tag, Options 8 | from classytags.arguments import Argument, ChoiceArgument 9 | 10 | from ..models import Seo 11 | 12 | register = template.Library() 13 | 14 | INTENTS = ['title', 'keywords', 'description'] 15 | 16 | 17 | class SeoTag(Tag): 18 | name = 'seo' 19 | options = Options( 20 | ChoiceArgument('intent', required=True, choices=INTENTS), 21 | 'for', 22 | Argument('instance', required=True), 23 | 'as', 24 | Argument('varname', resolve=False, required=False), 25 | ) 26 | 27 | def render_tag(self, context, intent, instance, varname): 28 | if isinstance(instance, Model): # hey we got a model instance 29 | seobj = Seo.objects.for_object(instance) 30 | else: # have no idea what is that? 31 | raise NotImplementedError 32 | 33 | value = getattr(seobj, intent, None) 34 | 35 | if varname: 36 | context[varname] = value 37 | return '' 38 | else: 39 | return escape(value or '') 40 | 41 | register.tag(SeoTag) 42 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [wheel] 2 | # create "py2.py3-none-any.whl" package 3 | universal = 1 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import re 5 | import sys 6 | import codecs 7 | 8 | from setuptools import setup, find_packages 9 | 10 | 11 | # When creating the sdist, make sure the django.mo file also exists: 12 | if 'sdist' in sys.argv or 'develop' in sys.argv: 13 | os.chdir('seo') 14 | try: 15 | from django.core import management 16 | management.call_command('compilemessages', stdout=sys.stderr, verbosity=1) 17 | except ImportError: 18 | if 'sdist' in sys.argv: 19 | raise 20 | finally: 21 | os.chdir('..') 22 | 23 | 24 | def read(*parts): 25 | file_path = os.path.join(os.path.dirname(__file__), *parts) 26 | return codecs.open(file_path, encoding='utf-8').read() 27 | 28 | 29 | def find_version(*parts): 30 | version_file = read(*parts) 31 | version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M) 32 | if version_match: 33 | return str(version_match.group(1)) 34 | raise RuntimeError("Unable to find version string.") 35 | 36 | 37 | setup( 38 | name="django-easy-seo", 39 | version=find_version('seo', '__init__.py'), 40 | license="GPLv3 License", 41 | 42 | install_requires=[ 43 | 'django-classy-tags', 44 | ], 45 | requires=[ 46 | 'Django (>=1.4)', 47 | ], 48 | 49 | description="Adds generic SEO fields for objects in your site", 50 | long_description=read('README.rst'), 51 | 52 | author="Alexander Ivanov", 53 | author_email="alexander.ivanov@redsolution.ru", 54 | 55 | maintainer='Basil Shubin', 56 | maintainer_email='basil.shubin@gmail.com', 57 | 58 | url='https://github.com/bashu/django-easy-seo', 59 | download_url='https://github.com/bashu/django-easy-seo/zipball/master', 60 | 61 | packages=find_packages(exclude=('example*',)), 62 | include_package_data=True, 63 | 64 | zip_safe=False, 65 | classifiers=[ 66 | 'Development Status :: 4 - Beta', 67 | 'Environment :: Web Environment', 68 | 'Framework :: Django', 69 | 'Intended Audience :: Developers', 70 | 'License :: OSI Approved :: GNU General Public License (GPL)', 71 | 'Operating System :: OS Independent', 72 | 'Programming Language :: Python', 73 | 'Programming Language :: Python :: 2.6', 74 | 'Programming Language :: Python :: 2.7', 75 | 'Programming Language :: Python :: 3', 76 | 'Programming Language :: Python :: 3.3', 77 | 'Topic :: Internet :: WWW/HTTP', 78 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 79 | 'Topic :: Internet :: WWW/HTTP :: Indexing/Search', 80 | 'Topic :: Software Development :: Libraries :: Python Modules', 81 | ], 82 | ) 83 | --------------------------------------------------------------------------------