From 1dfa7b36b382cd0069980ad93874b6bb9bb212b6 Mon Sep 17 00:00:00 2001 From: Ivan Sinyavskiy Date: Mon, 20 Jun 2022 11:53:11 +0300 Subject: [PATCH] Add package files --- .gitattributes | 2 + .gitignore | 56 ++------------- README.md | 2 - README.rst | 15 ++++ TODO.md | 3 + extend/__init__.py | 0 extend/admin.py | 22 ++++++ extend/apps.py | 5 ++ extend/migrations/__init__.py | 0 extend/models.py | 131 ++++++++++++++++++++++++++++++++++ extend/tests.py | 0 extend/utils.py | 25 +++++++ extend/views.py | 0 requirements.txt | 5 ++ setup.py | 37 ++++++++++ 15 files changed, 251 insertions(+), 52 deletions(-) create mode 100644 .gitattributes delete mode 100644 README.md create mode 100644 README.rst create mode 100644 TODO.md create mode 100644 extend/__init__.py create mode 100644 extend/admin.py create mode 100644 extend/apps.py create mode 100644 extend/migrations/__init__.py create mode 100644 extend/models.py create mode 100644 extend/tests.py create mode 100644 extend/utils.py create mode 100644 extend/views.py create mode 100644 requirements.txt create mode 100644 setup.py diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore index 55be276..582f2d0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -# ---> Python # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -21,7 +20,6 @@ parts/ sdist/ var/ wheels/ -share/python-wheels/ *.egg-info/ .installed.cfg *.egg @@ -40,17 +38,14 @@ pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ -.nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover -*.py,cover .hypothesis/ .pytest_cache/ -cover/ # Translations *.mo @@ -60,7 +55,6 @@ cover/ *.log local_settings.py db.sqlite3 -db.sqlite3-journal # Flask stuff: instance/ @@ -73,41 +67,16 @@ instance/ docs/_build/ # PyBuilder -.pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints -# IPython -profile_default/ -ipython_config.py - # pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version +.python-version -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff +# celery beat schedule file celerybeat-schedule -celerybeat.pid # SageMath parsed files *.sage.py @@ -133,22 +102,9 @@ venv.bak/ # mypy .mypy_cache/ -.dmypy.json -dmypy.json -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# PyCharm -# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +# Jetbrains PyCharm +.idea/ +# VSCode +.vscode/ diff --git a/README.md b/README.md deleted file mode 100644 index 2c2dba2..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# jeweller-extend - diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..6849891 --- /dev/null +++ b/README.rst @@ -0,0 +1,15 @@ +============= +jeweller-extend +============= + +Small collection of reusabled components for Django Projects. + + +Requirements: +************* + +* Django==2.2.9 +* django-model-utils==4.0.0 +* django-mptt==0.11.0 +* easy-thumbnails==2.7 +* pytils==0.3 diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..0ede432 --- /dev/null +++ b/TODO.md @@ -0,0 +1,3 @@ +# TODO + +* Написать тесты diff --git a/extend/__init__.py b/extend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/extend/admin.py b/extend/admin.py new file mode 100644 index 0000000..3c2c7d4 --- /dev/null +++ b/extend/admin.py @@ -0,0 +1,22 @@ +from django.contrib import admin +from mptt.admin import MPTTModelAdmin + + +class CategoryModelAdmin(MPTTModelAdmin): + list_display = ("id", "name", "slug") + + +class TagModelAdmin(admin.ModelAdmin): + list_display = ("id", "name", "slug") + + +def toggle_status_published(modeladmin, request, queryset): + queryset.update(status="published") + + +def toggle_status_moderation(modeladmin, request, queryset): + queryset.update(status="moderation") + + +toggle_status_published.short_description = "Опубликовать" +toggle_status_moderation.short_description = "Отправить на модерацию" diff --git a/extend/apps.py b/extend/apps.py new file mode 100644 index 0000000..902f769 --- /dev/null +++ b/extend/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ExtendConfig(AppConfig): + name = "extend" diff --git a/extend/migrations/__init__.py b/extend/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/extend/models.py b/extend/models.py new file mode 100644 index 0000000..82aca54 --- /dev/null +++ b/extend/models.py @@ -0,0 +1,131 @@ +from datetime import datetime +from django.db import models +from django.contrib.auth.models import User +from django.utils.safestring import mark_safe +from mptt.models import MPTTModel, TreeForeignKey +from pytils.translit import slugify +from model_utils import Choices +from easy_thumbnails.files import get_thumbnailer +from easy_thumbnails.fields import ThumbnailerImageField + + +class Category(MPTTModel): + name = models.CharField( + "Название", unique=True, max_length=100 + ) + slug = models.SlugField( + "Путь", + null=True, + blank=True, + unique=True, + max_length=100, + ) + parent = TreeForeignKey( + "self", + on_delete=models.CASCADE, + null=True, + blank=True, + related_name="children", + ) + + class MPTTMeta: + order_insertion_by = ["name"] + + class Meta: + abstract = True + verbose_name = "Категория" + verbose_name_plural = "Категории" + ordering = ["name"] + + def __str__(self): + return self.name + + def save(self, *args, **kwargs): + if not self.slug: + self.slug = slugify(self.name) + super().save(*args, **kwargs) + + +class Tag(models.Model): + name = models.CharField( + "Название", unique=True, max_length=100 + ) + slug = models.SlugField( + "Путь", + null=True, + blank=True, + unique=True, + max_length=100, + ) + + class Meta: + abstract = True + verbose_name = "Метка" + verbose_name_plural = "Метки" + ordering = ["name"] + + def __str__(self): + return self.name + + def save(self, *args, **kwargs): + if not self.slug: + self.slug = slugify(self.name) + super().save(*args, **kwargs) + + +class Photo(models.Model): + user = models.ForeignKey(User, null=True, on_delete=models.SET_NULL, verbose_name="Пользователь") + title = models.CharField("Название", blank=True, max_length=255) + image = ThumbnailerImageField( + "Фото", upload_to="photos/", max_length=255, default="", resize_source=dict(size=(825, 825), sharpen=True) + ) + created = models.DateTimeField("Создано", default=datetime.now) + updated = models.DateTimeField("Изменено", auto_now=True) + + class Meta: + abstract = True + verbose_name = "Фотография" + verbose_name_plural = "Фотографии" + ordering = ["id"] + + def __str__(self): + return self.title + + def image_tag(self): + if self.image: + options = {'size': (100, 100), 'upscale': True} + thumb_url = get_thumbnailer(self.image).get_thumbnail(options).url + return mark_safe('' % thumb_url) + else: + return 'Изображение отсутствует' + + image_tag.short_description = 'Превью' + + +class Comment(models.Model): + user = models.ForeignKey(User, null=True, on_delete=models.CASCADE) + text = models.TextField("Текст") + created = models.DateTimeField( + "Создано", auto_now_add=True + ) + updated = models.DateTimeField( + "Изменено", auto_now=True + ) + STATUS = Choices( + ("moderation", "На модерации"), + ("published", "Опубликовано"), + ) + status = models.CharField( + choices=STATUS, + default=STATUS.moderation, + max_length=20, + ) + + class Meta: + abstract = True + verbose_name = "Комментарий" + verbose_name_plural = "Комментарии" + ordering = ["-created"] + + def __str__(self): + return f'{self.created} {self.user.username}' diff --git a/extend/tests.py b/extend/tests.py new file mode 100644 index 0000000..e69de29 diff --git a/extend/utils.py b/extend/utils.py new file mode 100644 index 0000000..bb92d21 --- /dev/null +++ b/extend/utils.py @@ -0,0 +1,25 @@ +import re +from django.core.exceptions import ValidationError +from django.utils.crypto import get_random_string +from django.utils.translation import gettext_lazy as _ + + +def validate_url(value): + """ + Валидация ссылок на видео с видеохостинга YouTube + """ + video_url = value + regex = re.compile( + r"(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/(watch\?v=|embed/|v/|.+\?v=)?(?P[A-Za-z0-9\-=_]{11})" + ) + match = regex.match(video_url) + if not match: + raise ValidationError( + _("%(value)s не является корректной YouTube ссылкой"), + params={"value": value}, + ) + + +def generate_secret_key(): + chars = "abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)" + return get_random_string(50, chars) diff --git a/extend/views.py b/extend/views.py new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..046b243 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +Django==2.2.9 +django-model-utils==4.0.0 +django-mptt==0.11.0 +easy-thumbnails==2.7 +pytils==0.3 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..221f42e --- /dev/null +++ b/setup.py @@ -0,0 +1,37 @@ +import os +from setuptools import find_packages, setup + +with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as readme: + README = readme.read() + +# allow setup.py to be run from any path +os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) + +setup( + name='jeweller-extend', + version='0.2.1', + package_dir={'extend': 'extend'}, + packages=find_packages(), + include_package_data=True, + license='BSD License', + description='Small collection of reusabled components for Django.', + long_description=README, + url='https://github.com/stargot/jeweller-extend/', + author='Ivan Sinyavskiy', + author_email='stargot@gmail.com', + classifiers=[ + 'Environment :: Web Environment', + 'Framework :: Django', + 'Framework :: Django :: 2.2', + 'Framework :: Django :: 3.0', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', + ], +)