From a6c4c28825d0965daf266cb71478399ebbc55c83 Mon Sep 17 00:00:00 2001 From: Max Yakovenko Date: Sun, 19 Aug 2018 18:11:50 +0300 Subject: [PATCH] update core app --- core/admin.py | 27 ++++++- core/context_processors.py | 34 ++++++++ core/fixtures/cities.json | 1 + core/fixtures/currencies.json | 1 + core/forms.py | 18 +++++ core/middlewares.py | 0 core/models.py | 94 ++++++++++++++++++++-- core/templatetags/__init__.py | 0 core/templatetags/core_filters.py | 7 ++ core/templatetags/core_tags.py | 124 ++++++++++++++++++++++++++++++ core/utils.py | 6 ++ core/views.py | 25 +++++- 12 files changed, 326 insertions(+), 11 deletions(-) create mode 100644 core/fixtures/cities.json create mode 100644 core/fixtures/currencies.json create mode 100644 core/forms.py create mode 100644 core/middlewares.py create mode 100644 core/templatetags/__init__.py create mode 100644 core/templatetags/core_filters.py create mode 100644 core/templatetags/core_tags.py create mode 100644 core/utils.py diff --git a/core/admin.py b/core/admin.py index 96a7313..e188911 100644 --- a/core/admin.py +++ b/core/admin.py @@ -8,8 +8,10 @@ from django.db import router from django.template.response import TemplateResponse from django.utils.encoding import force_text from django.utils.translation import ugettext as _, ugettext_lazy +from jet.filters import DateRangeFilter +from rangefilter.filter import DateTimeRangeFilter -from core.models import AbstractStatusModel +from core.models import AbstractStatusModel, Certificate, City, Currency admin.site.site_header = 'Русские программы' admin.site.index_title = 'Главная страница' @@ -111,3 +113,26 @@ class SafeModelAdmin(admin.ModelAdmin): if 'delete_selected_safe' in actions and not issubclass(self.model, AbstractStatusModel): del actions['delete_selected_safe'] return actions + + +@admin.register(Certificate) +class CertAdmin(SafeModelAdmin): + list_display = ('name','image', 'status') + search_fields = ('name',) + list_filter = ('status',) + + +@admin.register(City) +class CityAdmin(admin.ModelAdmin): + list_display = ('name',) + search_fields = ('name',) + + +@admin.register(Currency) +class CurrencyAdmin(admin.ModelAdmin): + list_display = ('name', 'code', 'sign', 'value') + list_filter = ( + ('create_at', DateRangeFilter), ('updated_at', DateTimeRangeFilter) + ) + search_fields = ('name', 'code', 'sign') + diff --git a/core/context_processors.py b/core/context_processors.py index e69de29..d9704ee 100644 --- a/core/context_processors.py +++ b/core/context_processors.py @@ -0,0 +1,34 @@ +from django.urls import reverse_lazy + +from products.context_processors import product_categories, product_detail + + +def breadcrumbs(request): + view_name = request.resolver_match.view_name + crumbs = { + 'breadcrumb': {}, + 'is_index': view_name == 'index:index' + } + if crumbs['is_index']: + return crumbs + if 'products' in view_name: + crumbs['breadcrumb'] = _product_breadcrumbs(request=request) + elif 'news' in view_name: + crumbs['breadcrumb'] = _news_breadcrumbs(request=request) + return crumbs + + +def _product_breadcrumbs(request): + if 'list' in request.resolver_match.view_name: + breadcrumb = product_categories(request).get('the_product_category') + elif 'details' in request.resolver_match.view_name: + breadcrumb = product_detail(request).get('the_product') + else: + breadcrumb = None + + return breadcrumb + + + +def _news_breadcrumbs(request): + pass diff --git a/core/fixtures/cities.json b/core/fixtures/cities.json new file mode 100644 index 0000000..261fc30 --- /dev/null +++ b/core/fixtures/cities.json @@ -0,0 +1 @@ +[{"model": "core.city", "pk": 1, "fields": {"create_at": "2018-08-19T14:41:41.552Z", "updated_at": "2018-08-19T14:41:41.552Z", "name": "\u041c\u043e\u0441\u043a\u0432\u0430"}}] \ No newline at end of file diff --git a/core/fixtures/currencies.json b/core/fixtures/currencies.json new file mode 100644 index 0000000..83fa2c5 --- /dev/null +++ b/core/fixtures/currencies.json @@ -0,0 +1 @@ +[{"model": "core.currency", "pk": 1, "fields": {"create_at": "2018-08-19T14:41:00.257Z", "updated_at": "2018-08-19T14:41:00.257Z", "name": "\u0420\u0443\u0431\u043b\u044c", "code": "RUB", "sign": "\u20bd", "value": "1.00"}}] \ No newline at end of file diff --git a/core/forms.py b/core/forms.py new file mode 100644 index 0000000..4068e1b --- /dev/null +++ b/core/forms.py @@ -0,0 +1,18 @@ +from copy import copy +from django import forms +from django.urls import reverse_lazy + + +class QueryFormBase(forms.Form): + form_action = 'products:product_list' + query_params = {} + + def __init__(self, *args, **kwargs): + self.form_action = kwargs.pop('product_form_action','products:product_list') + self.query_params = copy(kwargs.pop('query_params', {})) + + super().__init__(*args, **kwargs) + + + def get_form_action_url(self): + return reverse_lazy(**self.form_action) diff --git a/core/middlewares.py b/core/middlewares.py new file mode 100644 index 0000000..e69de29 diff --git a/core/models.py b/core/models.py index ddb74d0..a5e73f6 100644 --- a/core/models.py +++ b/core/models.py @@ -1,8 +1,16 @@ +from abc import abstractmethod + from django.conf import settings from django.contrib.auth.base_user import BaseUserManager +from django.urls import reverse_lazy from django.utils.translation import ugettext_lazy as _ from django.db import models +from mptt import ( + models as mptt_models, + managers as mptt_managers, +) + # Create your models here. STATUS_NEW = 0 STATUS_ACTIVE = 25 @@ -17,6 +25,17 @@ STATUS_CHOICES = ( ) +class CaseInsensitiveQuerySet(models.QuerySet): + CASE_INSENSITIVE_FIELDS = ('email',) + + def _filter_or_exclude(self, negate, *args, **kwargs): + for field in self.CASE_INSENSITIVE_FIELDS: + if field in kwargs: + kwargs[field + '__iexact'] = kwargs[field] + del kwargs[field] + return super()._filter_or_exclude(negate, *args, **kwargs) + + class ActualOnlyManager(models.Manager): def get_queryset(self): queryset = super().get_queryset() @@ -35,6 +54,18 @@ class DeletedManager(models.Manager): return super().get_queryset().filter(status=STATUS_DELETED) +class ActualOnlyTreeManager(mptt_managers.TreeManager, ActualOnlyManager): + _base_manager = None + + +class ActiveOnlyTreeManager(mptt_managers.TreeManager, ActiveOnlyManager): + _base_manager = None + + +class DeletedTreeManager(mptt_managers.TreeManager, DeletedManager): + _base_manager = None + + class AbstractDateTimeModel(models.Model): create_at = models.DateTimeField(_('создан в'), auto_now_add=True) updated_at = models.DateTimeField(_('обновлен'), auto_now=True) @@ -73,12 +104,59 @@ class AbstractStatusModel(AbstractDateTimeModel): abstract = True -class CaseInsensitiveQuerySet(models.QuerySet): - CASE_INSENSITIVE_FIELDS = ('email',) +class AbstractStatusMPTTModel(mptt_models.MPTTModel, AbstractStatusModel): + objects = ActualOnlyTreeManager() + deleted = DeletedTreeManager() + active = ActiveOnlyTreeManager() - def _filter_or_exclude(self, negate, *args, **kwargs): - for field in self.CASE_INSENSITIVE_FIELDS: - if field in kwargs: - kwargs[field + '__iexact'] = kwargs[field] - del kwargs[field] - return super()._filter_or_exclude(negate, *args, **kwargs) + @property + def viewname(self): + raise NotImplementedError + + @property + def viewname_kwargs(self): + raise NotImplementedError + + def get_absolute_url(self): + return reverse_lazy(self.viewname, kwargs=self.viewname_kwargs) + + class Meta: + abstract = True + + +class Currency(AbstractDateTimeModel): + name = models.CharField(_('название'), max_length=255) + code = models.CharField(_('код валюты'), max_length=64) + sign = models.CharField(_('символ валюты'), max_length=10, blank=True, null=True) + value = models.DecimalField(_('Курс'), max_digits=6, decimal_places=2, help_text=_('Указывается в рублях')) + + def __str__(self): + return self.name + + class Meta: + verbose_name = _('Валюта') + verbose_name_plural = _('Валюта') + + +class City(AbstractDateTimeModel): + name = models.CharField(_('Город'), max_length=255) + + class Meta: + verbose_name = _('Город') + verbose_name_plural = _('Города') + + +class Certificate(AbstractStatusModel): + def upload_file_to(self, filename): + return "certificates/{filename}".format(**{ + 'category': self.name, + 'filename': filename + }) + + name = models.CharField(_('Название'), max_length=255) + image = models.FileField(_('Изображение'), upload_to=upload_file_to, blank=True,null=True) + preview = models.FileField(_('Миниатюрка'), upload_to=upload_file_to) + + class Meta: + verbose_name = _('Сертификат') + verbose_name_plural = _('Сертификаты') diff --git a/core/templatetags/__init__.py b/core/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/templatetags/core_filters.py b/core/templatetags/core_filters.py new file mode 100644 index 0000000..2034bf2 --- /dev/null +++ b/core/templatetags/core_filters.py @@ -0,0 +1,7 @@ +from django.template import Library + +register = Library() + +@register.filter +def get_item(dictionary, key): + return dictionary.get(key) diff --git a/core/templatetags/core_tags.py b/core/templatetags/core_tags.py new file mode 100644 index 0000000..b696cb5 --- /dev/null +++ b/core/templatetags/core_tags.py @@ -0,0 +1,124 @@ +from django import template +from django.template import loader, Node, Variable +from django.utils.encoding import smart_str, smart_bytes +from django.template.defaulttags import url +from django.template import VariableDoesNotExist +from mptt.templatetags.mptt_tags import recursetree + +register = template.Library() + +@register.tag +def breadcrumb(parser, token): + """ + Renders the breadcrumb. + Examples: + {% breadcrumb "Title of breadcrumb" url_var %} + {% breadcrumb context_var url_var %} + {% breadcrumb "Just the title" %} + {% breadcrumb just_context_var %} + + Parameters: + -First parameter is the title of the crumb, + -Second (optional) parameter is the url variable to link to, produced by url tag, i.e.: + {% url person_detail object.id as person_url %} + then: + {% breadcrumb person.name person_url %} + + @author Andriy Drozdyuk + """ + return BreadcrumbNode(token.split_contents()[1:]) + + +@register.tag +def breadcrumb_url(parser, token): + """ + Same as breadcrumb + but instead of url context variable takes in all the + arguments URL tag takes. + {% breadcrumb "Title of breadcrumb" person_detail person.id %} + {% breadcrumb person.name person_detail person.id %} + """ + + bits = token.split_contents() + if len(bits)==2: + return breadcrumb(parser, token) + + # Extract our extra title parameter + title = bits.pop(1) + token.contents = ' '.join(bits) + + url_node = url(parser, token) + + return UrlBreadcrumbNode(title, url_node) + +@register.tag +def breadcrumb_mptt_url(parser, token): + return recursetree(parser, token) + +class BreadcrumbNode(Node): + def __init__(self, vars): + """ + First var is title, second var is url context variable + """ + self.vars = map(Variable,vars) + + def render(self, context): + title = self.vars[0].var + + if title.find("'")==-1 and title.find('"')==-1: + try: + val = self.vars[0] + title = val.resolve(context) + except: + title = '' + + else: + title=title.strip("'").strip('"') + title=smart_bytes(title) + + url = None + + if len(self.vars)>1: + val = self.vars[1] + try: + url = val.resolve(context) + except VariableDoesNotExist: + print('URL does not exist', val) + url = None + + return create_crumb(title, url) + + +class UrlBreadcrumbNode(Node): + def __init__(self, title, url_node): + self.title = Variable(title) + self.url_node = url_node + + def render(self, context): + title = self.title.var + + if title.find("'")==-1 and title.find('"')==-1: + try: + val = self.title + title = val.resolve(context) + except: + title = '' + else: + title=title.strip("'").strip('"') + title=smart_bytes(title) + + url = self.url_node.render(context) + return create_crumb(title, url) + + +def create_crumb(title, url=None): + """ + Helper function + """ + crumb = """
  • %s""" + if url: + crumb = crumb.format(url, title) + else: + crumb = crumb.format('#', title) + + return crumb diff --git a/core/utils.py b/core/utils.py new file mode 100644 index 0000000..cc1f99a --- /dev/null +++ b/core/utils.py @@ -0,0 +1,6 @@ + +def parse_path(path): + try: + return path.split('/')[-2] # slug of the instance + except IndexError: + return None diff --git a/core/views.py b/core/views.py index 85cac90..5f099b1 100644 --- a/core/views.py +++ b/core/views.py @@ -1,19 +1,40 @@ from django.contrib.auth.mixins import LoginRequiredMixin # Create your views here. -from django.views.generic import TemplateView, ListView +from django.views import View +from django.views.generic import TemplateView, ListView, DetailView, FormView -class ProtectedView(LoginRequiredMixin, TemplateView): +class ProtectedView(LoginRequiredMixin, View): + pass + + +class ProtectedTemplateView(LoginRequiredMixin, TemplateView): pass class ProtectedListView(LoginRequiredMixin, ListView): pass + +class ProtectedDetailView(LoginRequiredMixin, DetailView): + pass + + +class ProtectedFormView(LoginRequiredMixin, FormView): + pass + + class Handler404View(TemplateView): template_name = '404.html' class Handler500View(TemplateView): template_name = '500.html' + + +class Handler403View(TemplateView): + template_name = '403.html' + +class Handler400View(TemplateView): + template_name = '400.html'