From 17b7f012dd7bccebe44f49bcb5264b37025977bb Mon Sep 17 00:00:00 2001 From: Max Yakovenko Date: Thu, 2 Aug 2018 23:10:36 +0300 Subject: [PATCH] bug fix --- README.md | 1 + accounts_ext/admin.py | 4 +- accounts_ext/forms.py | 7 +- accounts_ext/models.py | 5 - cabinet/views.py | 6 +- cart/models.py | 60 +++- cart/signals.py | 3 + cart/urls.py | 4 +- cart/views.py | 40 ++- core/views.py | 5 +- eshop_project/settings/base.py | 8 +- products/forms.py | 5 +- products/models.py | 86 +++-- products/urls.py | 5 +- products/views.py | 4 +- referral/admin.py | 38 +-- referral/forms.py | 18 +- referral/models.py | 32 +- static/css/main.fix.css | 7 + templates/accounts_ext/authentication.html | 6 +- templates/base.html | 2 + templates/cabinet/index.html | 6 +- templates/cart/bought_history.html | 24 +- templates/cart/cart.html | 170 ++++++++++ templates/common.html | 70 ++-- templates/components/breadcrumbs.html | 9 +- templates/components/footer.html | 4 +- templates/components/header.html | 4 +- templates/components/navbar.html | 7 + templates/products/product_category_list.html | 255 ++++++-------- templates/products/product_search.html | 321 ++++++++++-------- 31 files changed, 753 insertions(+), 463 deletions(-) create mode 100644 templates/cart/cart.html diff --git a/README.md b/README.md index e12556e..1fe4594 100644 --- a/README.md +++ b/README.md @@ -26,3 +26,4 @@ 2. Make migrations for modules 3. Collect static 4. Restart gunicorn-eshop and celery-eshop daemons in systemd +5. Add initial migration to core app and redefine site domain name diff --git a/accounts_ext/admin.py b/accounts_ext/admin.py index a15acc4..02166ac 100644 --- a/accounts_ext/admin.py +++ b/accounts_ext/admin.py @@ -31,7 +31,7 @@ class UserAdmin(SafeModelAdmin, BaseUserAdmin): ) fieldsets = ( - (None, {'fields': ('email', 'username', 'password', 'referral_code')}), + (None, {'fields': ('email', 'username', 'password', 'referral_user')}), (_('Permissions'), {'fields': ('status', 'is_superuser', 'groups', 'user_permissions')}), (_('Important dates'), {'fields': ('last_login', 'confirmed_at')}), @@ -41,7 +41,7 @@ class UserAdmin(SafeModelAdmin, BaseUserAdmin): (None, { 'classes': ('wide',), 'fields': ( - 'username', 'email', 'password1', 'password2', 'referral_code', 'status', 'is_superuser' + 'username', 'email', 'password1', 'password2', 'referral_user', 'status', 'is_superuser' ), }), ) diff --git a/accounts_ext/forms.py b/accounts_ext/forms.py index d0b8391..eed5cf5 100644 --- a/accounts_ext/forms.py +++ b/accounts_ext/forms.py @@ -21,6 +21,7 @@ from .models import Profile, Company logger = logging.getLogger(__name__) + class RegistrationForm(RegistrationFormUniqueEmail): email = forms.EmailField(label=_("E-mail"), widget=forms.EmailInput(attrs={'class': 'reg__text'})) password1 = forms.CharField( @@ -261,14 +262,12 @@ class CompanyForm(forms.Form): class UserChangeForm(UserChangeFormBase): - class Meta: model = get_user_model() - fields = ('status','referral_code',) + fields = ('status', 'referral_user',) class UserCreationForm(UserCreationFormBase): - def clean_email(self): email = self.cleaned_data['email'] if DisposableEmailChecker().is_disposable(email): @@ -277,7 +276,7 @@ class UserCreationForm(UserCreationFormBase): class Meta: model = get_user_model() - fields = ('email', 'username', 'status', 'referral_code', 'is_superuser') + fields = ('email', 'username', 'status', 'referral_user', 'is_superuser') field_classes = { 'email': forms.EmailField, 'username': UsernameField, diff --git a/accounts_ext/models.py b/accounts_ext/models.py index 64866fc..9d5304d 100644 --- a/accounts_ext/models.py +++ b/accounts_ext/models.py @@ -46,11 +46,6 @@ class User(AbstractStatusModel, PermissionsMixin, AbstractBaseUser): }, ) - referral_code = models.ForeignKey('referral.Referral', - verbose_name=_('Реферальная ссылка'), - on_delete=models.SET_NULL, - blank=True, null=True - ) referral_user = models.ForeignKey('self', verbose_name=_('Реферальный пользователь'), on_delete=models.SET_NULL, diff --git a/cabinet/views.py b/cabinet/views.py index c637eaa..2328b7b 100644 --- a/cabinet/views.py +++ b/cabinet/views.py @@ -29,7 +29,7 @@ class IndexView(ProtectedView): return paginator.page(the_page) def get_full_name(self,user): - return '{last_name}{first_name}{patronymic}'.format(**{ + return '{last_name} {first_name} {patronymic}'.format(**{ 'last_name': user.profile.last_name or "", 'first_name': user.profile.first_name or "", 'patronymic': user.profile.patronymic or "" @@ -43,6 +43,6 @@ class IndexView(ProtectedView): context['full_name'] = self.get_full_name(self.request.user) context['email'] = self.request.user.email context['phone_number'] = self.request.user.profile.phone - context['company'] = self.request.user.company - context['profile'] = self.request.user.profile + context['company'] = self.request.user.company or None + context['profile'] = self.request.user.profile or None return context diff --git a/cart/models.py b/cart/models.py index b8218c0..988f98e 100644 --- a/cart/models.py +++ b/cart/models.py @@ -1,12 +1,13 @@ from django.db import models from django.contrib.auth import get_user_model from django.contrib.postgres.fields import HStoreField +from django.db.models import Avg from django.utils.translation import ugettext_lazy as _ from autoslug import AutoSlugField # Create your models here. -from core.models import AbstractStatusModel +from core.models import AbstractStatusModel, AbstractDateTimeModel from products.models import Product OFFER_STATUS_ACTIVE = 25 @@ -20,16 +21,23 @@ OFFER_STATUS_CHOICES = ( OFFER_DEFAULT_CHOICE = OFFER_STATUS_INACTIVE +class SupplyType(AbstractDateTimeModel): + name = models.CharField(max_length=255) + term = models.ValueRange() + + class Offer(AbstractStatusModel): - name = models.CharField(max_length=64, blank=True, null=True, default=None) - slug = AutoSlugField(populate_from='name') + product = models.OneToOneField(Product, on_delete=models.CASCADE, primary_key=True) price = models.DecimalField(max_digits=8, decimal_places=2, null=True, default=0.00) - product = models.ForeignKey(Product, on_delete=models.CASCADE, blank=True, null=True, default=None, - related_name='variants') - attributes = HStoreField(blank=True, null=True, default={}) - + currency = models.CharField(max_length=64, blank=True, null=True, default=None) + with_nds = models.BooleanField(default=False) + # attributes = HStoreField(blank=True, null=True, default={}) + suply_type = models.ForeignKey(SupplyType, on_delete=models.SET_NULL, blank=True, null=True) + note = models.TextField(blank=True, null=True) status = models.SmallIntegerField(_('статус'), default=OFFER_DEFAULT_CHOICE, choices=OFFER_STATUS_CHOICES) + amount = models.IntegerField(blank=True, null=True) + def __str__(self): return self.name @@ -44,7 +52,7 @@ BUYING_STATUS_PENDING = 50 BUYING_STATUS_BOUGHT = 75 BUYING_STATUS_CHOICES = ( (BUYING_STATUS_IN_CART, _('В корзине')), - (BUYING_STATUS_PENDING,_('Обрабатываеться')), + (BUYING_STATUS_PENDING, _('Обрабатываеться')), (BUYING_STATUS_BOUGHT, _('Куплен')) ) @@ -52,10 +60,44 @@ BUYING_DEFAULT_CHOICE = BUYING_STATUS_IN_CART class Buying(AbstractStatusModel): - user = models.ForeignKey(get_user_model(),verbose_name=_('пользователь'), on_delete=models.CASCADE) + user = models.ForeignKey(get_user_model(), verbose_name=_('пользователь'), on_delete=models.CASCADE) offer = models.ForeignKey(Offer, verbose_name=_('позиция'), on_delete=models.CASCADE) status = models.SmallIntegerField(_('статус'), default=BUYING_DEFAULT_CHOICE, choices=BUYING_STATUS_CHOICES) + amount = models.SmallIntegerField(_('колличество'), default=0) class Meta: verbose_name = _('Покупка') verbose_name_plural = _('Покупки') + + +STATUS_GAINED = 0 +STATUS_SPENT = 100 +CASHBACK_STATUS_CHOICES = ( + (STATUS_GAINED, _('заработанный')), + (STATUS_SPENT, _('потраченный')), +) + +STATUS_DEFAULT = STATUS_GAINED + + +class CashBackManager(models.Manager): + def get_gained_cashback_sum(self, user): + return self.get_queryset().filter(user=user, status=STATUS_GAINED).aggregate(Avg('amount')) + + def get_spent_cashback_sum(self, user): + return self.get_queryset().filter(user=user, status=STATUS_SPENT).aggregate(Avg('amount')) + + +class BuyingCashback(AbstractDateTimeModel): + user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE) + buying = models.OneToOneField(Buying, on_delete=models.CASCADE) + amount = models.DecimalField(_('Сумма'), 'cashback', 7, 2, default=0) + status = models.SmallIntegerField(_('статус'), default=STATUS_DEFAULT, choices=CASHBACK_STATUS_CHOICES) + + objects = CashBackManager() + + class Meta: + verbose_name_plural = _('cashback') + verbose_name = _('cashback') + + diff --git a/cart/signals.py b/cart/signals.py index e69de29..5e111d0 100644 --- a/cart/signals.py +++ b/cart/signals.py @@ -0,0 +1,3 @@ +from django.dispatch import Signal + +buying_bought = Signal(providing_args=['buying']) diff --git a/cart/urls.py b/cart/urls.py index e7e8dd2..8e77ac2 100644 --- a/cart/urls.py +++ b/cart/urls.py @@ -7,6 +7,6 @@ urlpatterns = [ # url(r'^$', views.CartDetail, name='CartDetail'), # url(r'^remove/(?P[-\w]+)/$', views.CartRemove, name='CartRemove'), # url(r'^add/$', views.CartAdd, name='CartAdd'), - re_path(r'^buyings/history/', views.BuyingsHistory.as_view(), name='buyings_history') - + re_path(r'^history/', views.BuyingsHistory.as_view(), name='history'), + re_path(r'^buyings/$', views.CartView.as_view(), name='buyings'), ] diff --git a/cart/views.py b/cart/views.py index 7390597..2d9898c 100644 --- a/cart/views.py +++ b/cart/views.py @@ -5,12 +5,15 @@ from django.views.decorators.csrf import csrf_exempt from django.contrib.auth.decorators import login_required from django.contrib import auth from django.views.generic import ListView +from django.utils.translation import ugettext_lazy as _ from cart.models import Buying -from core.views import ProtectedView +from core.views import ProtectedListView from .cart import Cart from .forms import CartAddProductForm + + # from discount.layout import DiscountApllyForm # @csrf_exempt @@ -56,16 +59,37 @@ from .forms import CartAddProductForm # # 'discount_apply_form': discount_apply_form}) -class BuyingsHistory(ListView,ProtectedView): +class CartView(ProtectedListView): model = Buying - paginate_by = settings.DEFAULT_PAGE_AMMOUNT - context_object_name = 'bought_item_list' - ordering = '-created_at' - template_name = 'cart/bought_history.html' + paginate_by = settings.DEFAULT_PAGE_AMOUNT + context_object_name = 'cart_items' + template_name = 'cart/cart.html' + ordering = '-create_at' + title = _('Корзина') def get_queryset(self): - self.object_list = super().get_queryset() - return self.object_list + qs = super().get_queryset() + return qs.filter(user=self.request.user) + + def get_context_data(self, *, object_list=None, **kwargs): + context = super().get_context_data(object_list=object_list, **kwargs) + context['title'] = self.title + return context +class BuyingsHistory(ProtectedListView): + model = Buying + paginate_by = settings.DEFAULT_PAGE_AMOUNT + context_object_name = 'bought_item_list' + ordering = '-create_at' + template_name = 'cart/bought_history.html' + title = _('История покупок') + + def get_queryset(self): + qs = super().get_queryset() + return qs.filter(user=self.request.user) + def get_context_data(self, *, object_list=None, **kwargs): + context = super().get_context_data(object_list=object_list, **kwargs) + context['title'] = self.title + return context diff --git a/core/views.py b/core/views.py index d0410a9..85cac90 100644 --- a/core/views.py +++ b/core/views.py @@ -1,13 +1,16 @@ from django.contrib.auth.mixins import LoginRequiredMixin # Create your views here. -from django.views.generic import TemplateView +from django.views.generic import TemplateView, ListView class ProtectedView(LoginRequiredMixin, TemplateView): pass +class ProtectedListView(LoginRequiredMixin, ListView): + pass + class Handler404View(TemplateView): template_name = '404.html' diff --git a/eshop_project/settings/base.py b/eshop_project/settings/base.py index 54b9dd1..4d4c5f7 100644 --- a/eshop_project/settings/base.py +++ b/eshop_project/settings/base.py @@ -284,4 +284,10 @@ CART_SESSION_ID = 'cart' IMPORT_EXPORT_USE_TRANSACTIONS = True # PAGINATION SETTINGS -DEFAULT_PAGE_AMMOUNT = 10 +DEFAULT_PAGE_AMOUNT = 10 + +# DEFAULT REFERRAL POINTS +DEFAULT_REFERRAL_POINTS = 120 + +# CASHBACK RATIO IN PERCENTAGE +CASHBACK_RATIO = 4 diff --git a/products/forms.py b/products/forms.py index 776bbff..113d678 100644 --- a/products/forms.py +++ b/products/forms.py @@ -29,7 +29,7 @@ # query += u'"%s"' % sqs.query.clean(producer) # sqs = sqs.narrow(u'brand_exact:%s' % query) # return sqs -from crispy_forms.layout import Layout, ButtonHolder, Submit, HTML, Field, Button +from crispy_forms.layout import Layout, Field, Button from django import forms from crispy_forms.helper import FormHelper from django.urls import reverse_lazy @@ -38,6 +38,9 @@ from django.utils.translation import ugettext_lazy as _ from .models import Product +class ProductFilterForm(forms.Form): + pass + class ProductSearchForm(forms.ModelForm): field_template = 'bootstrap/forms/product_search.html' form_action = reverse_lazy('products:product_search') diff --git a/products/models.py b/products/models.py index e2846d8..494eeaa 100644 --- a/products/models.py +++ b/products/models.py @@ -1,3 +1,4 @@ +from django.contrib.auth import get_user_model from django.db import models from django.urls import reverse_lazy from django.contrib.postgres.fields import HStoreField @@ -11,14 +12,41 @@ from mptt import ( from mptt.models import MPTTModel, TreeForeignKey from autoslug import AutoSlugField -from core.models import AbstractStatusModel, AbstractDateTimeModel, STATUS_ACTIVE, STATUS_DELETED +from core.models import AbstractStatusModel, AbstractDateTimeModel, STATUS_ACTIVE, STATUS_DELETED, ActualOnlyManager + +# --------------------------------- PRODUCT ATTRIBUTE ------------------------------------# +PRODUCT_ATTRIBUTE_TYPE_NONE = 0 +PRODUCT_ATTRIBUTE_TYPE_RANGE = 50 +PRODUCT_ATTRIBUTE_TYPE_SELECT = 100 + +PRODUCT_ATTRIBUTE_TYPE_CHOICES = ( + (PRODUCT_ATTRIBUTE_TYPE_NONE, _('никакой')), + (PRODUCT_ATTRIBUTE_TYPE_RANGE, _('диапазон')), + (PRODUCT_ATTRIBUTE_TYPE_SELECT, _('выбор')) +) +PRODUCT_ATTRIBUTE_TYPE_DEFAULT = PRODUCT_ATTRIBUTE_TYPE_NONE + + +class ProductAttributeManager(models.Manager): + def get_range_type_attributes(self, product=None): + pass + + def get_select_type_attributes(self, product=None): + pass + + def get_all_type_attributes(self): + pass class ProductAttribute(AbstractStatusModel): name = models.CharField(max_length=64, blank=True, null=True, default=None) slug = AutoSlugField(populate_from='name') + type = models.SmallIntegerField(_('тип'), choices=PRODUCT_ATTRIBUTE_TYPE_CHOICES, + default=PRODUCT_ATTRIBUTE_TYPE_DEFAULT) main_attribute = models.BooleanField(default=False) + objects = ProductAttributeManager() + def __str__(self): return self.name @@ -31,7 +59,7 @@ class ProductAttribute(AbstractStatusModel): class Manufacturer(AbstractStatusModel): name = models.CharField(max_length=64, blank=True, null=True, default=None) slug = AutoSlugField(populate_from='name') - image = models.ImageField(upload_to='producers', blank=True, verbose_name="image of producer") + image = models.ImageField(upload_to='producers', blank=True, null=True, verbose_name=("Изображение")) def __str__(self): return self.name @@ -45,7 +73,7 @@ class Manufacturer(AbstractStatusModel): verbose_name_plural = _('Производители') -class ProductCategoryManager(mptt_managers.TreeManager): +class ProductCategoryManager(mptt_managers.TreeManager, ActualOnlyManager): def get_root_categories(self): return self.get_queryset().all() @@ -75,21 +103,18 @@ register(ProductCategory, order_insertion_py=['name']) class Product(AbstractStatusModel): - name = models.CharField(max_length=64, db_index=True, blank=True, null=True, default=None) - slug = AutoSlugField(populate_from='name') - price = models.DecimalField(max_digits=10, decimal_places=2, default=0.00) - description = models.TextField(db_index=True, blank=True, null=True, default=None) - manufacturer = models.ForeignKey(Manufacturer, on_delete=models.PROTECT) - category = models.ForeignKey(ProductCategory, on_delete=models.SET_NULL, blank=True, null=True, default=None) + name = models.CharField(_('имя'), max_length=64, db_index=True) + slug = AutoSlugField(_('slug'), populate_from='name', db_index=True) + description = models.TextField(_('описание'), blank=True, null=True, default=None) + manufacturer = models.ForeignKey(Manufacturer, on_delete=models.SET_NULL, blank=True, null=True) + category = models.ForeignKey(ProductCategory, on_delete=models.SET_NULL, blank=True, null=True) attributes = models.ManyToManyField(ProductAttribute, blank=True) - discount_policy = HStoreField(blank=True, null=True, default={}) - is_active = models.BooleanField(default=True) def __str__(self): return self.name - def get_absolute_url(self): - return reverse_lazy('products:item', args=[self.slug]) + def get_absolute_url(self, request): + return request.build_absolute_uri(reverse_lazy('products:item', args=[self.slug])) class Meta: indexes = [ @@ -112,19 +137,38 @@ class Product(AbstractStatusModel): # pp.save() -class ProductAttributeValue(AbstractDateTimeModel): - product = models.ForeignKey(Product, on_delete=models.PROTECT) - name = models.CharField(max_length=64, blank=True, null=True, default=None) +class ProductRate(AbstractStatusModel): + user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE) + product = models.ForeignKey(Product, on_delete=models.CASCADE) + rate = models.IntegerField(_('оценка'), default=0) + + class Meta: + verbose_name = _('Рейтинг продукта') + verbose_name_plural = _('Рейтинг продукта') + + +class ProductDiscount(AbstractStatusModel): + product = models.ForeignKey(Product, on_delete=models.CASCADE) + percentage = models.DecimalField(_('процент'), max_digits=3, decimal_places=2) + status = models.BooleanField(_('статус'), default=False) + + class Meta: + verbose_name = _('Дисконт') + verbose_name_plural = _('Дисконты') + + +class ProductAttributeValue(AbstractStatusModel): + attribute = models.ForeignKey(ProductAttribute, on_delete=models.CASCADE, related_name='value') slug = AutoSlugField(populate_from='name') - attribute = models.ForeignKey(ProductAttribute, on_delete=models.CASCADE, related_name='values') + value = HStoreField(_('значение'), default={}) def __str__(self): - return self.name + return self.value.serialize class Meta: - unique_together = ('name', 'attribute') - verbose_name = 'Product attribute value' - verbose_name_plural = 'Product attribute values' + unique_together = ('attribute', 'value') + verbose_name = _('Значение аттрибута') + verbose_name_plural = _('Значение аттрибутов') # ----------------- PRODUCT IMAGE STATUS LIST ------------------ diff --git a/products/urls.py b/products/urls.py index 672f93e..5f50893 100644 --- a/products/urls.py +++ b/products/urls.py @@ -20,10 +20,9 @@ from .models import ProductCategory urlpatterns = [ - re_path(r'^product/category/search/$', views.ProductSearchView.as_view(), name='product_search'), + re_path(r'^search/$', views.ProductSearchView.as_view(), name='product_search'), re_path(r'^product/(?P\w+)/$', views.ProductDetailsView.as_view(), name='product_details'), - re_path(r'^product/category/list/$', views.ProductCategoryListView.as_view(), name='product_category'), - + re_path(r'^category/list/$', views.ProductCategoryListView.as_view(), name='product_category'), # Uncomment for elasticsearch # url(r'^autocomplete/$', autocomplete), diff --git a/products/views.py b/products/views.py index 4e34b61..9649381 100644 --- a/products/views.py +++ b/products/views.py @@ -28,7 +28,7 @@ class ProductSearchView(ListView): model = Product template_name = 'products/product_search.html' context_object_name = 'products' - paginate_by = settings.DEFAULT_PAGE_AMMOUNT + paginate_by = settings.DEFAULT_PAGE_AMOUNT title = _('Поиск товара') def get_queryset(self): @@ -47,7 +47,7 @@ class ProductListView(ListView): model = Product template_name = 'products/product_list.html' context_object_name = 'products' - paginate_by = settings.DEFAULT_PAGE_AMMOUNT + paginate_by = settings.DEFAULT_PAGE_AMOUNT title = _('Список товаров') def get_queryset(self): diff --git a/referral/admin.py b/referral/admin.py index 8554dfd..2773d13 100644 --- a/referral/admin.py +++ b/referral/admin.py @@ -8,8 +8,8 @@ from django.utils.translation import ugettext_lazy as _ from rangefilter.filter import DateRangeFilter, DateTimeRangeFilter from core.admin import SafeModelAdmin -from .models import Referral, ReferralStats, PartnerStats -from .forms import ReferralAdminForm, ReferralStatsAdminForm, PartnerStatsAdminForm +from .models import Referral, ReferralStats +from .forms import ReferralAdminForm, ReferralStatsAdminForm class ReferralAdminInline(admin.TabularInline): @@ -69,37 +69,3 @@ class ReferralStatsAdmin(SafeModelAdmin): list_select_related = ('referral',) search_fields = ('referral__name', 'referral__code',) ordering = ('-create_at',) - - -@register(PartnerStats) -class PartnerStatsAdmin(SafeModelAdmin): - def has_add_permission(self, request): - return False - - def stats_owner(self, stats): - try: - link = reverse_lazy( - 'admin:{}_{}_change'.format(stats.user._meta.app_label, stats.user._meta.object_name.lower()), - args=(stats.user.id,) - ) - name = stats.user.email - except Exception as e: - link = '#' - name = "None" - return format_html('{}', link, name) - - stats_owner.short_description = _('User') - - def formatted_total_conversion(self, stats): - return str(stats.formatted_total_conversion) + "%" - - formatted_total_conversion.short_description = _('Total conversion') - - def converted_total_earnings(self, stats): - return stats.total_earnings if stats.total_earnings > 0 else 0 - - converted_total_earnings.short_description = _('Total earnings') - - form = PartnerStatsAdminForm - list_display = ('stats_owner', 'total_visits', 'total_regs') - search_fields = ('user__email',) diff --git a/referral/forms.py b/referral/forms.py index 0f61adb..304076f 100644 --- a/referral/forms.py +++ b/referral/forms.py @@ -1,7 +1,7 @@ from django import forms -from django.forms import URLInput, ALL_FIELDS +from django.forms import ALL_FIELDS -from .models import Referral, ReferralStats, PartnerStats +from .models import Referral, ReferralStats class ReferralAdminForm(forms.ModelForm): @@ -30,17 +30,3 @@ class ReferralStatsAdminForm(forms.ModelForm): def disable_fields(self, fields): for field_name, field in fields.items(): field.disabled = True - - -class PartnerStatsAdminForm(forms.ModelForm): - class Meta: - model = PartnerStats - fields = ALL_FIELDS - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.disable_fields(self.fields) - - def disable_fields(self, fields): - for field_name, field in fields.items(): - field.disabled = True diff --git a/referral/models.py b/referral/models.py index 8f89ef6..180c57b 100644 --- a/referral/models.py +++ b/referral/models.py @@ -15,6 +15,8 @@ from django.utils.translation import ugettext_lazy as _ # Create your models here. from registration.signals import user_activated +from cart.models import Buying +from cart.signals import buying_bought from core.models import AbstractStatusModel, STATUS_ACTIVE, STATUS_CHOICES from referral.mixins import StatsFormatterMixin from referral.utils import get_cookie @@ -30,7 +32,7 @@ REFERRAL_DEFAULT_CHOICE = STATUS_ACTIVE class Referral(AbstractStatusModel): CODE_LENGTH = 8 - referral_owner = models.OneToOneField(get_user_model(), verbose_name=_('пользователь'), on_delete=models.CASCADE, + user = models.OneToOneField(get_user_model(), verbose_name=_('пользователь'), on_delete=models.CASCADE, primary_key=True ) code = models.CharField(_('код'), max_length=255) @@ -38,7 +40,7 @@ class Referral(AbstractStatusModel): @classmethod def create(cls, user, code): - referral = cls(referral_owner=user, code=code) + referral = cls(user=user, code=code) return referral @staticmethod @@ -69,23 +71,12 @@ class ReferralStats(StatsFormatterMixin, AbstractStatusModel): referral = models.OneToOneField(Referral, verbose_name=_('Реферал'), on_delete=models.CASCADE, primary_key=True) visits = models.IntegerField(_('Посищения'), default=0) registrations = models.IntegerField(_('Регистрации'), default=0) + points = models.IntegerField(_('партенрские балы'), default=0) class Meta: verbose_name = _('Реферальная статистика') verbose_name_plural = _('Реферальная статистика') - -class PartnerStats(StatsFormatterMixin, AbstractStatusModel): - user = models.OneToOneField(get_user_model(), verbose_name=_('username'), on_delete=models.CASCADE, - primary_key=True) - total_visits = models.BigIntegerField(_('Всего посещений'), default=0) - total_regs = models.BigIntegerField(_('Всего регистраций'), default=0) - - class Meta: - verbose_name = _('Партнеская статистика') - verbose_name_plural = _('Партнерская статистика') - - @receiver(post_save, sender=get_user_model()) def create_user_referral(sender, instance, created, **kwargs): if created and Referral.objects.filter(user=instance).first() is None: @@ -95,8 +86,6 @@ def create_user_referral(sender, instance, created, **kwargs): @receiver(post_save, sender=Referral) def init_referral_stuff(sender, instance, created, *args, **kwargs): if created: - if PartnerStats.objects.filter(user=instance.referral_owner).count() == 0: - PartnerStats.objects.create(user=instance.referral_owner).save() if ReferralStats.active.filter(referral=instance).count() == 0: ReferralStats.active.create(referral=instance).save() @@ -108,11 +97,14 @@ def update_ref_stats(sender, user, request, **kwargs): referral = Referral.objects.filter(code__exact=referral_code).first() if referral: referral.referralstats.registrations += 1 + referral.referralstats.points += settings.DEFAULT_REFERRAL_POINTS referral.referralstats.save() - referral.user.partnerstats.total_regs += 1 - referral.user.partnerstats.save() - user.referral_code = referral - user.referral_user = referral.referral_owner + user.referral_user = referral.user user.save() else: logger.warning("Missing referral code in database: " + referral_code) + + +@receiver(buying_bought,sender=Buying) +def charge_referral_points(sender,buying,*args,**kwargs): + pass diff --git a/static/css/main.fix.css b/static/css/main.fix.css index 17bb8af..b6a006c 100644 --- a/static/css/main.fix.css +++ b/static/css/main.fix.css @@ -1,3 +1,10 @@ .reg__text__center { text-align: center; } + +@media (max-width: 991.98px) { + .mb-10 { + margin-bottom: 30px !important; + } + +} diff --git a/templates/accounts_ext/authentication.html b/templates/accounts_ext/authentication.html index 5863ccc..9ed6f1b 100644 --- a/templates/accounts_ext/authentication.html +++ b/templates/accounts_ext/authentication.html @@ -2,7 +2,7 @@ {% load static %} {% load crispy_forms_tags %} -{% block title %}Вход{% endblock %} +{% block title %}{{ title }}{% endblock %} {% block content %} @@ -13,6 +13,10 @@
Вход
+
+
Вход
+
+ {% crispy form %} diff --git a/templates/base.html b/templates/base.html index 000919c..31b4c1f 100644 --- a/templates/base.html +++ b/templates/base.html @@ -39,6 +39,8 @@
+ {% block content_message %} + {% endblock content_message %} {% block content %} {% endblock content %}
diff --git a/templates/cabinet/index.html b/templates/cabinet/index.html index 801539d..04b39e7 100644 --- a/templates/cabinet/index.html +++ b/templates/cabinet/index.html @@ -21,7 +21,7 @@
- Ваша ссылка: {{ ref_link }} + Ваша ссылка: {{ ref_link }}
Скопировать @@ -38,7 +38,7 @@ {% for ref_user in referral_user_list %}
{{ ref_user.name }}
-
{{ ref_user. }}
+
{{ ref_user }}
{% empty %}
diff --git a/templates/cart/bought_history.html b/templates/cart/bought_history.html index 365b5d9..8f23bee 100644 --- a/templates/cart/bought_history.html +++ b/templates/cart/bought_history.html @@ -2,7 +2,12 @@ {% block content %}
-
История покупок
+ +
+

История покупок

+
@@ -29,6 +34,23 @@ {% endfor %}
Дата
+
{% endblock content %} diff --git a/templates/cart/cart.html b/templates/cart/cart.html new file mode 100644 index 0000000..741897b --- /dev/null +++ b/templates/cart/cart.html @@ -0,0 +1,170 @@ +{% extends 'base.html' %} +{% block content %} +
+
+
Корзина
+
+
+
+
+ Товар +
+
+ Цена +
+
+ Количество +
+
+ Сумма +
+
+
+
+
+
+
+
+
+ +
Microsoft Windows 7 Professional SP1 + (x32/x64) GGK [6PC-00024] +
+
+
+
+
+
+
+
Цена
+
10 000 ₽
+
+
+
Количество
+
2 шт
+
+
+
Сумма
+
20 000 ₽
+
+
+
+
+
+
+
+
+
+
+
+ +
Microsoft Windows 7 Professional SP1 + (x32/x64) GGK [6PC-00024] +
+
+
+
+
+
+
+
Цена
+
10 000 ₽
+
+
+
Количество
+
2 шт
+
+
+
Сумма
+
20 000 ₽
+
+
+
+
+
+
+
+
+
+
+
+ +
Microsoft Windows 7 Professional SP1 + (x32/x64) GGK [6PC-00024] +
+
+
+
+
+
+
+
Цена
+
10 000 ₽
+
+
+
Количество
+
2 шт
+
+
+
Сумма
+
20 000 ₽
+
+
+
+
+
+
+
+
+
+
+
+ +
Microsoft Windows 7 Professional SP1 + (x32/x64) GGK [6PC-00024] +
+
+
+
+
+
+
+
Цена
+
10 000 ₽
+
+
+
Количество
+
2 шт
+
+
+
Сумма
+
20 000 ₽
+
+
+
+
+
+
+
Итого: 20 000 ₽
+
Кэшбек 800 ₽
+
+ +
+
+
+{% endblock content %} diff --git a/templates/common.html b/templates/common.html index 7987941..746ecec 100644 --- a/templates/common.html +++ b/templates/common.html @@ -4,47 +4,42 @@ {% block content %}
-
- {% if left_product_search_form %} - - {% endif %} + {% if left_product_search_form %} + + {% endif %} - {% block extra_left_menu_items %} - {% endblock extra_left_menu_items %} - {% if product_root_categories %} -
- {% for category in product_root_categories %} - + {% block extra_left_menu_items %} + {% endblock extra_left_menu_items %} + + {% if product_root_categories %} + - {% endif %} - {% if contact_us_form %} + {{ category.name }} + + {% endfor %} +
+ {% endif %} + {% if contact_us_form %}
Не нашли нужную программу или разработчика? Заполните форму и с Вами свяжется наш специалист.
{% if contact_us_form_message %} - {% if "success" in contact_us_form_message.tags %} -
- {% elif "error" in contact_us_form_message.tags %} -
- {% endif %} - {{ contact_us_form_message }} -
+
+ {{ contact_us_form_message }} +
{% endif %} {% crispy contact_us_form %}
- {% endif %} -
+ {% endif %}
@@ -62,6 +57,25 @@ {% endif %}
+ {% if product_root_categories %} + + {% endif %} + {% include 'components/breadcrumbs.html' %} {% block right_common_content %} {% endblock right_common_content %} diff --git a/templates/components/breadcrumbs.html b/templates/components/breadcrumbs.html index dfcef98..6e77b2c 100644 --- a/templates/components/breadcrumbs.html +++ b/templates/components/breadcrumbs.html @@ -1,2 +1,7 @@ -{% block breadcrumbs %} -{% endblock breadcrumbs %} +{% if request.resolver_match.url_name != "index" %} + +{% endif %} diff --git a/templates/components/footer.html b/templates/components/footer.html index dab181b..b2ec929 100644 --- a/templates/components/footer.html +++ b/templates/components/footer.html @@ -4,12 +4,12 @@
diff --git a/templates/products/product_category_list.html b/templates/products/product_category_list.html index a5c0fd6..99174e7 100644 --- a/templates/products/product_category_list.html +++ b/templates/products/product_category_list.html @@ -1,26 +1,58 @@ {% extends 'common.html' %} -{% block extra_left_menu_items %} +{% load static %} +{% block extra_left_menu_items %} +
+
+ Фильтр товаров +
+
+ Цена +
+
+
+ от + + до + +
+
+
+
+
+
Производитель
+ +
+
+
Тип поставки
+ +
+
+
Назначение
+ +
+
+
{% endblock extra_left_menu_items %} {% block right_common_content %} - -
Производители
  • Avast
  • @@ -42,142 +74,73 @@
-
-
-
-
- -
-
- -
+ {% for product in products %} +
+
+
+ {{ product.name }}
-
79 400 ₽ / 1398 $
-
-
    -
  • Артикул:057J1-WWW8695-T548
  • -
  • НДС:Включен
  • -
  • Платформа:Windows
  • -
  • Тип поставки:Любой из доступных
  • -
  • Срок поставки:3-14 дней
  • -
  • Примечание:Временная лицензия, 1 год – новая покупка, локальная
  • -
+
+ +
+ {{ product.offer.price }} {{ product.offer.currency }} +
+
+
    +
  • Артикул:057J1-WWW8695-T548
  • +
  • НДС:Включен
  • +
  • Платформа:Windows
  • +
  • Тип поставки:Любой из доступных
  • +
  • Срок поставки:3-14 дней
  • +
  • Примечание:Временная лицензия, 1 год – новая покупка, локальная
  • +
+
-
- -
-
-
-
-
- -
-
- -
-
-
79 400 ₽ / 1398 $
-
-
    -
  • Артикул:057J1-WWW8695-T548
  • -
  • НДС:Включен
  • -
  • Платформа:Windows
  • -
  • Тип поставки:Любой из доступных
  • -
  • Срок поставки:3-14 дней
  • -
  • Примечание:Временная лицензия, 1 год – новая покупка, локальная
  • -
-
+
- -
-
-
-
-
- -
-
- -
-
-
79 400 ₽ / 1398 $
-
-
    -
  • Артикул:057J1-WWW8695-T548
  • -
  • НДС:Включен
  • -
  • Платформа:Windows
  • -
  • Тип поставки:Любой из доступных
  • -
  • Срок поставки:3-14 дней
  • -
  • Примечание:Временная лицензия, 1 год – новая покупка, локальная
  • -
+ {% empty %} +
+
+
+
+ +
+
+ +
+
+
79 400 ₽ / 1398 $
+
+
    +
  • Артикул:057J1-WWW8695-T548
  • +
  • НДС:Включен
  • +
  • Платформа:Windows
  • +
  • Тип поставки:Любой из доступных
  • +
  • Срок поставки:3-14 дней
  • +
  • Примечание:Временная лицензия, 1 год – новая покупка, локальная
  • +
+
-
- -
-
-
-
-
- -
-
- -
-
-
79 400 ₽ / 1398 $
-
-
    -
  • Артикул:057J1-WWW8695-T548
  • -
  • НДС:Включен
  • -
  • Платформа:Windows
  • -
  • Тип поставки:Любой из доступных
  • -
  • Срок поставки:3-14 дней
  • -
  • Примечание:Временная лицензия, 1 год – новая покупка, локальная
  • -
-
+
- -
+ {% endfor %}
diff --git a/templates/products/product_search.html b/templates/products/product_search.html index 5869149..a31cbc5 100644 --- a/templates/products/product_search.html +++ b/templates/products/product_search.html @@ -1,27 +1,58 @@ {% extends 'common.html' %} -{% block title %}{{ title }}{% endblock title %} {% block extra_left_menu_items %} - +
+
+ Фильтр товаров +
+
+ Цена +
+ +
+ от + + до + +
+
+ +
+
+
Производитель
+ +
+
+
Тип поставки
+ +
+
+
Назначение
+ +
+
+
{% endblock extra_left_menu_items %} {% block right_common_content %} - - + +
Производители
  • Avast
  • @@ -43,142 +74,144 @@
-
-
-
-
- -
-
- -
-
-
79 400 ₽ / 1398 $
-
-
    -
  • Артикул:057J1-WWW8695-T548
  • -
  • НДС:Включен
  • -
  • Платформа:Windows
  • -
  • Тип поставки:Любой из доступных
  • -
  • Срок поставки:3-14 дней
  • -
  • Примечание:Временная лицензия, 1 год – новая покупка, локальная
  • -
+ {% for product in products %} +
+
+
+
+ +
+
+ +
+
+
79 400 ₽ / 1398 $
+
+
    +
  • Артикул:057J1-WWW8695-T548
  • +
  • НДС:Включен
  • +
  • Платформа:Windows
  • +
  • Тип поставки:Любой из доступных
  • +
  • Срок поставки:3-14 дней
  • +
  • Примечание:Временная лицензия, 1 год – новая покупка, локальная
  • +
+
+
- -
-
-
-
-
- -
-
- -
-
-
79 400 ₽ / 1398 $
-
-
    -
  • Артикул:057J1-WWW8695-T548
  • -
  • НДС:Включен
  • -
  • Платформа:Windows
  • -
  • Тип поставки:Любой из доступных
  • -
  • Срок поставки:3-14 дней
  • -
  • Примечание:Временная лицензия, 1 год – новая покупка, локальная
  • -
+
+
+
+
+ +
+
+ +
+
+
79 400 ₽ / 1398 $
+
+
    +
  • Артикул:057J1-WWW8695-T548
  • +
  • НДС:Включен
  • +
  • Платформа:Windows
  • +
  • Тип поставки:Любой из доступных
  • +
  • Срок поставки:3-14 дней
  • +
  • Примечание:Временная лицензия, 1 год – новая покупка, локальная
  • +
+
+
- -
-
-
-
-
- -
-
- -
-
-
79 400 ₽ / 1398 $
-
-
    -
  • Артикул:057J1-WWW8695-T548
  • -
  • НДС:Включен
  • -
  • Платформа:Windows
  • -
  • Тип поставки:Любой из доступных
  • -
  • Срок поставки:3-14 дней
  • -
  • Примечание:Временная лицензия, 1 год – новая покупка, локальная
  • -
+
+
+
+
+ +
+
+ +
+
+
79 400 ₽ / 1398 $
+
+
    +
  • Артикул:057J1-WWW8695-T548
  • +
  • НДС:Включен
  • +
  • Платформа:Windows
  • +
  • Тип поставки:Любой из доступных
  • +
  • Срок поставки:3-14 дней
  • +
  • Примечание:Временная лицензия, 1 год – новая покупка, локальная
  • +
+
+
- -
-
-
-
-
- -
-
- -
-
-
79 400 ₽ / 1398 $
-
-
    -
  • Артикул:057J1-WWW8695-T548
  • -
  • НДС:Включен
  • -
  • Платформа:Windows
  • -
  • Тип поставки:Любой из доступных
  • -
  • Срок поставки:3-14 дней
  • -
  • Примечание:Временная лицензия, 1 год – новая покупка, локальная
  • -
+
+
+
+
+ +
+
+ +
+
+
79 400 ₽ / 1398 $
+
+
    +
  • Артикул:057J1-WWW8695-T548
  • +
  • НДС:Включен
  • +
  • Платформа:Windows
  • +
  • Тип поставки:Любой из доступных
  • +
  • Срок поставки:3-14 дней
  • +
  • Примечание:Временная лицензия, 1 год – новая покупка, локальная
  • +
+
+
- -
+ {% endfor %}