Skip to content

Commit

Permalink
#273 Do not remove products (#443)
Browse files Browse the repository at this point in the history
* Remove ProductPage.objects.delete(...) in favor of ProductPage.objects.update(is_active=True). Add is_active=True to model's filters. Create todo

* Self-review fixes

* Review fixes

* Fix typo

* Apply coala rules
  • Loading branch information
ArtemijRodionov committed Jul 24, 2018
1 parent 7f98a33 commit 36a9dd5
Show file tree
Hide file tree
Showing 9 changed files with 63 additions and 50 deletions.
10 changes: 4 additions & 6 deletions front/js/components/order.es6
Original file line number Diff line number Diff line change
Expand Up @@ -53,22 +53,20 @@
* Bind events to parent's elements, because of dynamic elements.
*/
DOM.$order.on('click', DOM.submit, submitOrder);
DOM.$order.on('click', DOM.remove, event => removeProduct(
getElAttr(event, 'productId'), getElAttr(event, 'productCount'),
));
DOM.$order.on('click', DOM.remove, event => removeProduct(getElAttr(event, 'productId'), getElAttr(event, 'productCount')));
DOM.$order.on('change', DOM.productCount, helpers.debounce(changeProductCount, 250));
DOM.$order.on('keyup', 'input', event => storeInput($(event.target)));
}

function getProductsData() {
return $(DOM.productRows).map((_, el) => {
let $el = $(el);
const $el = $(el);
return {
id: $el.attr('data-table-id'),
name: $el.find('.js-product-link').text(),
quantity: $el.find('.js-prod-count').val(),
}
}).get()
};
}).get();
}

/**
Expand Down
2 changes: 1 addition & 1 deletion front/js/components/product.es6
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@
};

server.addToCart(id, count)
.then(data => {
.then((data) => {
mediator.publish('onCartUpdate', data);
mediator.publish('onProductAdd', [id, count]);
});
Expand Down
16 changes: 8 additions & 8 deletions front/js/shared/yandex.es6
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@
window.dataLayer = window.dataLayer || [];
// Load ecommerce plugin for gaTracker
try {
ga('require', 'ecommerce');
ga('require', 'ecommerce'); // Ignore ESLintBear (block-scoped-var)
} catch (e) {
var ga = console.error;
var ga = console.error; // Ignore ESLintBear (no-var)
console.error(`GaTracker failed to load. Traceback: ${e}`);
}

let yaTracker = new YATracker(window.dataLayer, 'RUB');
let gaTracker = new GATracker(ga, 'ecommerce');
const yaTracker = new YATracker(window.dataLayer, 'RUB'); // Ignore ESLintBear (no-undef)
const gaTracker = new GATracker(ga, 'ecommerce'); // Ignore ESLintBear (block-scoped-var)

const init = () => {
setUpListeners();
Expand All @@ -50,19 +50,19 @@
reachGoal('FULL_BUY_SEND');
// Use a dummy order's id, because we do not wait complete processing of
// purchase request.
let orderData = {id: 'DummyId'};
const orderData = { id: 'DummyId' };
yaTracker.purchase(products, orderData);
gaTracker.purchase(products, orderData);
});
// We receive an onProductAdd event from a category and a product pages
mediator.subscribe('onProductAdd', (_, id, count) => {
yaTracker.add([{id: id, quantity: count}]);
yaTracker.add([{ id, quantity: count }]);
});
mediator.subscribe('onProductRemove', (_, id, count) => {
reachGoal('DELETE_PRODUCT');
yaTracker.remove([{id: id, quantity: count}]);
yaTracker.remove([{ id, quantity: count }]);
});
mediator.subscribe('onProductDetail', (_, id) => yaTracker.detail([{id: id}]));
mediator.subscribe('onProductDetail', (_, id) => yaTracker.detail([{ id }]));
mediator.subscribe('onBackCallSend', () => reachGoal('BACK_CALL_SEND'));

DOM.$searchForm.submit(() => reachGoal('USE_SEARCH_FORM'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,12 +237,11 @@ def report(recipients=None, message=None):
@transaction.atomic
def delete(data: Dict[UUID, Data]):
uuids = list(data)
page_count, _ = ProductPage.objects.exclude(
shopelectro_product__uuid__in=uuids).delete()
product_count, _ = Product.objects.exclude(
uuid__in=uuids).delete()
logger.info('{} products and {} pages were deleted.'.format(
product_count, page_count))
pages_to_deactive = ProductPage.objects.exclude(
shopelectro_product__uuid__in=uuids)
pages_to_deactive.update(is_active=False)
deactive_count = pages_to_deactive.count()
logger.info(f'{deactive_count} products and {deactive_count} pages were deleted.')


@transaction.atomic
Expand Down
2 changes: 1 addition & 1 deletion shopelectro/management/commands/excel.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def collapse_row(row):
def write_product_rows():
"""Write products lines."""
sheet = self.sheet
products = Product.objects.filter(category=category)
products = Product.objects.filter(category=category, page__is_active=True)
for product in products.iterator():
product_start = 'A' + self.CURRENT_ROW
sheet[product_start] = product.name
Expand Down
2 changes: 1 addition & 1 deletion shopelectro/management/commands/price.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def prepare_products(categories_, utm):
.select_related('page')
.prefetch_related('category')
.prefetch_related('page__images')
.filter(category__in=categories_, price__gt=0)
.filter(category__in=categories_, price__gt=0, page__is_active=True)
)

if utm == 'YM':
Expand Down
10 changes: 7 additions & 3 deletions shopelectro/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from django.urls import reverse
from django.utils.text import slugify
from django.utils.translation import ugettext_lazy as _
from mptt.models import TreeManager
from mptt.querysets import TreeQuerySet
from unidecode import unidecode

Expand All @@ -18,7 +17,12 @@
CategoryManager,
)
from ecommerce.models import Order as ecOrder
from pages.models import CustomPage, ModelPage, Page, SyncPageMixin
from pages.models import CustomPage, ModelPage, Page, SyncPageMixin, PageManager

# @todo #273 Create a custom manager for the Product model.
# Currently we have code dupliactions for such filter:
# Product.objects.filter(page__is_active=True)
# Filter Product's queryset in initial method.


class SECategoryQuerySet(TreeQuerySet):
Expand Down Expand Up @@ -298,7 +302,7 @@ def serialize_tags_to_title(tags: TagQuerySet) -> str:
)


class ExcludedModelTPageManager(TreeManager):
class ExcludedModelTPageManager(PageManager):

def get_queryset(self):
return super().get_queryset().exclude(type=Page.MODEL_TYPE)
Expand Down
40 changes: 26 additions & 14 deletions shopelectro/views/catalog.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from functools import partial

from django import http
from django.conf import settings
from django.core.paginator import Paginator, InvalidPage
from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
from django.shortcuts import render, get_object_or_404
from django.views.decorators.http import require_POST
from django_user_agents.utils import get_user_agent
Expand All @@ -29,7 +29,7 @@ def get_paginated_page_or_404(objects, per_page, page_number):
try:
return Paginator(objects, per_page).page(page_number)
except InvalidPage:
raise Http404('Page does not exist')
raise http.Http404('Page does not exist')


# CATALOG VIEWS
Expand All @@ -45,11 +45,23 @@ class ProductPage(catalog.ProductPage):

queryset = (
models.Product.objects
.filter(category__isnull=False)
.filter(category__isnull=False, page__is_active=True)
.prefetch_related('product_feedbacks', 'page__images')
.select_related('page')
)

def get(self, request, *args, **kwargs):
try:
self.object = self.get_object()
except http.Http404 as error404:
# @todo #273 Render 404, that is recommending products for a deleted product.
# 1. Find a product with page__is_active=False
# 2. If the product exists then render 404 with products of its category
# See the parent issue for details.
raise error404
context = self.get_context_data(object=self.object)
return self.render_to_response(context)

def get_context_data(self, **kwargs):
context = super(ProductPage, self).get_context_data(**kwargs)

Expand Down Expand Up @@ -77,7 +89,7 @@ def get_context_data(self, **kwargs):

top_products = (
models.Product.objects
.filter(id__in=settings.TOP_PRODUCTS)
.filter(id__in=settings.TOP_PRODUCTS, page__is_active=True)
.prefetch_related('category')
.select_related('page')
)
Expand Down Expand Up @@ -134,7 +146,7 @@ def get_context_data(self, **kwargs):
page_number < 1 or
products_on_page not in settings.CATEGORY_STEP_MULTIPLIERS
):
raise Http404('Page does not exist.')
raise http.Http404('Page does not exist.')

all_products = (
models.Product.objects
Expand Down Expand Up @@ -181,7 +193,7 @@ def template_context(page, tag_titles, tags):
total_products = all_products.count()
products = paginated_page.object_list
if not products:
raise Http404('Page without products does not exist.')
raise http.Http404('Page without products does not exist.')

return {
**context,
Expand Down Expand Up @@ -212,11 +224,11 @@ def load_more(request, category_slug, offset=0, limit=0, sorting=0, tags=None):
products_on_page = limit or get_products_count(request)
offset = int(offset)
if offset < 0:
return HttpResponseBadRequest(
return http.HttpResponseBadRequest(
'The offset is wrong. An offset should be greater than or equal to 0.'
)
if products_on_page not in settings.CATEGORY_STEP_MULTIPLIERS:
return HttpResponseBadRequest(
return http.HttpResponseBadRequest(
'The limit number is wrong. List of available numbers:'
f' {", ".join(map(str, settings.CATEGORY_STEP_MULTIPLIERS))}'
)
Expand Down Expand Up @@ -265,29 +277,29 @@ def get_keys_from_post(*args):
return {arg: request.POST.get(arg, '') for arg in args}

product_id = request.POST.get('id')
product = models.Product.objects.filter(id=product_id).first()
product = models.Product.objects.filter(id=product_id, page__is_active=True).first()
if not (product_id and product):
return HttpResponse(status=422)
return http.HttpResponse(status=422)

fields = ['rating', 'name', 'dignities', 'limitations', 'general']
feedback_data = get_keys_from_post(*fields)

models.ProductFeedback.objects.create(product=product, **feedback_data)
return HttpResponse('ok')
return http.HttpResponse('ok')


@require_POST
def delete_feedback(request):
if not request.user.is_authenticated:
return HttpResponseForbidden('Not today, sly guy...')
return http.HttpResponseForbidden('Not today, sly guy...')

feedback_id = request.POST.get('id')
feedback = models.ProductFeedback.objects.filter(id=feedback_id).first()
if not (feedback_id and feedback):
return HttpResponse(status=422)
return http.HttpResponse(status=422)

feedback.delete()
return HttpResponse('Feedback with id={} was deleted.'.format(feedback_id))
return http.HttpResponse('Feedback with id={} was deleted.'.format(feedback_id))


class ProductsWithoutImages(catalog.ProductsWithoutImages):
Expand Down
20 changes: 10 additions & 10 deletions shopelectro/views/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,20 @@ def get_redirect_search_entity(self):
search_entities = [
search_engine.Search(
name='category',
qs=Category.objects.all(),
fields=['name'],
qs=Category.objects.filter(page__is_active=True),
fields=['name'], # Ignore CPDBear
min_similarity=settings.TRIGRAM_MIN_SIMILARITY,
),
search_engine.Search(
name='product',
qs=Product.objects.all(),
qs=Product.objects.filter(page__is_active=True),
fields=['name'],
redirect_field='vendor_code',
min_similarity=settings.TRIGRAM_MIN_SIMILARITY,
),
search_engine.Search(
name='page',
qs=ExcludedModelTPage.objects.all(),
qs=ExcludedModelTPage.objects.get_active(),
fields=['name'],
min_similarity=settings.TRIGRAM_MIN_SIMILARITY,
)
Expand All @@ -41,21 +41,21 @@ class Autocomplete(search_views.AutocompleteView):
search_entities = [
search_engine.Search(
name='category',
qs=Category.objects.all(),
qs=Category.objects.filter(page__is_active=True),
fields=['name', 'id'],
template_fields=['name', 'url'],
min_similarity=settings.TRIGRAM_MIN_SIMILARITY,
),
search_engine.Search(
name='product',
qs=Product.objects.all(),
qs=Product.objects.filter(page__is_active=True),
fields=['name', 'id', 'vendor_code'],
template_fields=['name', 'price', 'url'],
min_similarity=settings.TRIGRAM_MIN_SIMILARITY,
),
search_engine.Search(
name='pages',
qs=ExcludedModelTPage.objects.all(),
qs=ExcludedModelTPage.objects.get_active(),
fields=['name'],
template_fields=['name', 'url'],
min_similarity=settings.TRIGRAM_MIN_SIMILARITY,
Expand All @@ -71,19 +71,19 @@ class AdminAutocomplete(search_views.AdminAutocompleteView):
search_entities = [
search_engine.Search(
name='category',
qs=Category.objects.all(),
qs=Category.objects.filter(page__is_active=True),
fields=['name'],
min_similarity=settings.TRIGRAM_MIN_SIMILARITY,
),
search_engine.Search(
name='product',
qs=Product.objects.all(),
qs=Product.objects.filter(page__is_active=True),
fields=['name'],
min_similarity=settings.TRIGRAM_MIN_SIMILARITY,
),
search_engine.Search(
name='pages',
qs=ExcludedModelTPage.objects.all(),
qs=ExcludedModelTPage.objects.get_active(),
fields=['name'],
min_similarity=settings.TRIGRAM_MIN_SIMILARITY,
)
Expand Down

2 comments on commit 36a9dd5

@0pdd
Copy link
Collaborator

@0pdd 0pdd commented on 36a9dd5 Jul 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Puzzle 273-9b2b9d3f discovered in shopelectro/views/catalog.py and submitted as #444. Please, remember that the puzzle was not necessarily added in this particular commit. Maybe it was added earlier, but we discovered it only now.

@0pdd
Copy link
Collaborator

@0pdd 0pdd commented on 36a9dd5 Jul 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Puzzle 273-4fe77e3c discovered in shopelectro/models.py and submitted as #445. Please, remember that the puzzle was not necessarily added in this particular commit. Maybe it was added earlier, but we discovered it only now.

Please sign in to comment.